<!DOCTYPE html>
<html lang="en" class="dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PromptManager - Gallery</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.6/viewer.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.6/viewer.min.js"></script>
    <script>
        tailwind.config = {
            darkMode: 'class',
            theme: {
                extend: {
                    colors: {
                        gray: {
                            850: '#1f2937',
                            875: '#1a202c',
                            925: '#0f172a'
                        }
                    }
                }
            }
        }
    </script>
    <style>
        .custom-scrollbar::-webkit-scrollbar {
            width: 8px;
        }
        .custom-scrollbar::-webkit-scrollbar-track {
            background: #374151;
            border-radius: 4px;
        }
        .custom-scrollbar::-webkit-scrollbar-thumb {
            background: #6b7280;
            border-radius: 4px;
        }
        .custom-scrollbar::-webkit-scrollbar-thumb:hover {
            background: #9ca3af;
        }
        
        .viewer-dark-theme .viewer-backdrop {
            background-color: #000000 !important;
            background: #000000 !important;
        }
        
        /* Additional selectors to ensure black background */
        .viewer-backdrop {
            background-color: #000000 !important;
            background: #000000 !important;
        }
        
        .viewer-container {
            background-color: #000000 !important;
            background: #000000 !important;
        }
        
        .viewer-canvas {
            background-color: #000000 !important;
            background: #000000 !important;
        }
        
        .viewer-dark-theme .viewer-toolbar {
            background-color: rgba(31, 41, 55, 0.95) !important;
        }
        
        .viewer-dark-theme .viewer-navbar {
            background-color: rgba(31, 41, 55, 0.95) !important;
        }
        
        .viewer-dark-theme .viewer-title {
            color: #f3f4f6 !important;
        }

        .image-item {
            transition: transform 0.2s ease, box-shadow 0.2s ease;
        }
        
        .image-item:hover {
            transform: translateY(-2px);
            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
        }

        .metadata-sidebar {
            position: fixed !important;
            top: 0 !important;
            right: 0 !important;
            width: 384px !important;
            height: 100vh !important;
            background: rgb(17, 24, 39) !important;
            border-left: 1px solid rgb(55, 65, 81) !important;
            display: flex !important;
            flex-direction: column !important;
            z-index: 10000 !important;
            transition: transform 0.3s ease-in-out !important;
        }
        
        .metadata-sidebar.collapsed {
            transform: translateX(340px) !important;
        }
        
        .metadata-collapse-btn {
            position: absolute;
            left: -40px;
            top: 50%;
            transform: translateY(-50%);
            width: 32px;
            height: 80px;
            background: rgb(17, 24, 39);
            border: 1px solid rgb(55, 65, 81);
            border-right: none;
            border-radius: 6px 0 0 6px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.3s ease;
            z-index: 10001;
        }
        
        .metadata-collapse-btn:hover {
            background: rgb(31, 41, 55);
        }
        
        .metadata-collapse-btn svg {
            transition: transform 0.3s ease;
        }
        
        .metadata-sidebar.collapsed .metadata-collapse-btn svg {
            transform: rotate(180deg);
        }
    </style>
</head>
<body class="bg-gray-925 text-gray-100 min-h-screen">
    <div class="min-h-screen flex flex-col">
        <!-- Header -->
        <header class="bg-gradient-to-r from-gray-850 to-gray-800 border-b border-gray-700 px-6 py-4">
            <div class="max-w-7xl mx-auto">
                <div class="flex items-center justify-between">
                    <div class="flex items-center space-x-3">
                        <div class="w-10 h-10 bg-gradient-to-br from-violet-500 to-purple-600 rounded-lg flex items-center justify-center">
                            <span class="text-xl">🖼️</span>
                        </div>
                        <div>
                            <h1 class="text-2xl font-bold bg-gradient-to-r from-violet-400 to-purple-400 bg-clip-text text-transparent">
                                PromptManager Gallery
                            </h1>
                            <p class="text-gray-400 text-sm">Browse all images from your ComfyUI output folder</p>
                        </div>
                    </div>
                    <div class="hidden sm:flex items-center space-x-2">
                        <div class="w-3 h-3 bg-green-500 rounded-full animate-pulse"></div>
                        <span class="text-green-400 text-sm font-medium">Online</span>
                    </div>
                </div>
            </div>
        </header>

        <!-- Controls -->
        <div class="bg-gray-850 border-b border-gray-700/50 px-6 py-4">
            <div class="max-w-7xl mx-auto">
                <div class="flex flex-wrap items-center justify-between gap-4">
                    <div class="flex items-center space-x-4">
                        <label class="text-sm text-gray-400">Show:</label>
                        <select id="limitSelector" class="px-3 py-1 bg-gray-700 border border-gray-600 rounded text-gray-100 text-sm">
                            <option value="50">50</option>
                            <option value="100" selected>100</option>
                            <option value="200">200</option>
                            <option value="500">500</option>
                        </select>
                        <span class="text-sm text-gray-400">images</span>
                    </div>
                    <div class="flex items-center space-x-2">
                        <button id="refreshBtn" 
                                class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white text-sm font-medium rounded-lg transition-colors">
                            🔄 Refresh
                        </button>
                        <button id="gridViewBtn" 
                                class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg transition-colors">
                            ⊞ Grid View
                        </button>
                        <button id="listViewBtn" 
                                class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg transition-colors">
                            ☰ List View
                        </button>
                        <button id="settingsBtn" 
                                class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white text-sm font-medium rounded-lg transition-colors">
                            ⚙️ Settings
                        </button>
                    </div>
                </div>
            </div>
        </div>

        <!-- Stats Bar -->
        <div class="bg-gray-800 px-6 py-3 border-b border-gray-700/50">
            <div class="max-w-7xl mx-auto">
                <div class="flex items-center justify-between text-sm">
                    <span class="text-gray-400">
                        Showing <span id="showingStart">0</span> to <span id="showingEnd">0</span> of <span id="totalImages">0</span> images
                    </span>
                    <span class="text-gray-400" id="loadingStatus">Loading...</span>
                </div>
            </div>
        </div>

        <!-- Gallery Content -->
        <div class="flex-1 overflow-y-auto custom-scrollbar">
            <div class="max-w-7xl mx-auto p-6">
                <!-- Loading State -->
                <div id="loadingState" class="flex items-center justify-center py-20">
                    <div class="text-center">
                        <div class="w-12 h-12 border-4 border-violet-500/30 border-t-violet-500 rounded-full animate-spin mx-auto mb-4"></div>
                        <p class="text-gray-400">Loading gallery...</p>
                    </div>
                </div>

                <!-- Error State -->
                <div id="errorState" class="hidden flex items-center justify-center py-20">
                    <div class="text-center">
                        <div class="w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center mx-auto mb-4">
                            <span class="text-3xl">❌</span>
                        </div>
                        <h3 class="text-xl font-semibold text-gray-100 mb-2">Error Loading Gallery</h3>
                        <p class="text-gray-400 mb-4" id="errorMessage">Something went wrong while loading the images.</p>
                        <button id="retryBtn" class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white font-medium rounded-lg transition-colors">
                            🔄 Try Again
                        </button>
                    </div>
                </div>

                <!-- Gallery Grid -->
                <div id="galleryGrid" class="hidden grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-8 gap-4">
                    <!-- Images will be inserted here -->
                </div>

                <!-- Gallery List -->
                <div id="galleryList" class="hidden space-y-3">
                    <!-- Images will be inserted here -->
                </div>

                <!-- Pagination -->
                <div id="paginationControls" class="hidden mt-8 flex items-center justify-center space-x-2">
                    <button id="prevPageBtn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
                        ← Previous
                    </button>
                    <div class="flex items-center space-x-2">
                        <span class="text-gray-400">Page</span>
                        <input id="pageInput" type="number" min="1" class="w-16 px-2 py-1 bg-gray-700 text-white text-center rounded border border-gray-600 focus:border-blue-500 focus:outline-none" value="1">
                        <span id="pageInfo" class="text-gray-400">of 1</span>
                    </div>
                    <button id="nextPageBtn" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
                        Next →
                    </button>
                </div>
            </div>
        </div>
    </div>

    <!-- Settings Modal -->
    <div id="settingsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
        <div class="bg-gray-800 rounded-xl max-w-2xl w-full max-h-[90vh] overflow-hidden mx-4 border border-gray-700">
            <div class="p-6 border-b border-gray-700">
                <div class="flex items-center justify-between">
                    <h3 class="text-xl font-semibold text-gray-100">⚙️ Gallery Settings</h3>
                    <button id="closeSettingsBtn" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
                        <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
                        </svg>
                    </button>
                </div>
            </div>
            <div class="p-6 overflow-y-auto max-h-[calc(90vh-200px)] custom-scrollbar space-y-6">
                
                <!-- Performance Settings -->
                <div class="space-y-4">
                    <h4 class="text-lg font-medium text-gray-100 flex items-center">
                        <span class="mr-2">🚀</span>
                        Performance Settings
                    </h4>
                    
                    <!-- Thumbnail Generation -->
                    <div class="bg-gray-700 rounded-lg p-4">
                        <div class="flex items-center justify-between mb-3">
                            <div>
                                <h5 class="text-white font-medium">Generate Thumbnails</h5>
                                <p class="text-sm text-gray-400">Create optimized thumbnails for faster loading</p>
                            </div>
                            <button id="generateThumbnailsBtn" class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white text-sm rounded transition-colors">
                                Generate
                            </button>
                        </div>
                        <div class="text-xs text-gray-500 mb-2">
                            Status: <span id="thumbnailStatus">Not generated</span>
                            <div class="text-xs text-gray-600 mt-1">
                                💡 Thumbnails speed up gallery loading. Original images always used for viewing and metadata.
                            </div>
                        </div>
                        <!-- Check thumbnails at startup -->
                        <div class="flex items-center justify-between py-2 border-t border-gray-600">
                            <div>
                                <label class="text-sm text-gray-300">Check thumbnails at startup</label>
                                <p class="text-xs text-gray-500">Prompt to generate thumbnails if missing when opening gallery</p>
                            </div>
                            <label class="relative inline-flex items-center cursor-pointer">
                                <input type="checkbox" id="checkThumbnailsAtStartup" class="sr-only peer" checked>
                                <div class="w-11 h-6 bg-gray-600 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-violet-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-violet-600"></div>
                            </label>
                        </div>
                        <!-- Progress bar (hidden by default) -->
                        <div id="thumbnailProgress" class="hidden">
                            <div class="flex items-center justify-between text-xs text-gray-400 mb-1">
                                <span id="progressText">Processing...</span>
                                <div class="flex items-center gap-2">
                                    <span id="progressPercent">0%</span>
                                    <button id="cancelThumbnailsBtn" class="text-red-400 hover:text-red-300 text-xs px-2 py-1 bg-red-900/20 rounded hidden">
                                        Cancel
                                    </button>
                                </div>
                            </div>
                            <div class="w-full bg-gray-600 rounded-full h-2">
                                <div id="progressBar" class="bg-violet-600 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
                            </div>
                            <div class="flex items-center justify-between text-xs text-gray-500 mt-1">
                                <span id="progressDetails">Scanning images...</span>
                                <span id="progressETA" class="text-gray-600"></span>
                            </div>
                        </div>
                    </div>

                    <!-- Image Loading -->
                    <div class="bg-gray-700 rounded-lg p-4">
                        <h5 class="text-white font-medium mb-3">Image Loading</h5>
                        <div class="space-y-3">
                            <div class="flex items-center justify-between">
                                <label class="text-sm text-gray-300">Lazy Loading</label>
                                <input type="checkbox" id="lazyLoadingToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                            </div>
                            <div class="flex items-center justify-between">
                                <label class="text-sm text-gray-300">Image Quality</label>
                                <select id="imageQualitySelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
                                    <option value="high">High (Original)</option>
                                    <option value="medium" selected>Medium (Optimized)</option>
                                    <option value="low">Low (Fast)</option>
                                </select>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Display Settings -->
                <div class="space-y-4">
                    <h4 class="text-lg font-medium text-gray-100 flex items-center">
                        <span class="mr-2">🖼️</span>
                        Display Settings
                    </h4>
                    
                    <div class="bg-gray-700 rounded-lg p-4 space-y-3">
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Default View Mode</label>
                            <select id="defaultViewModeSelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
                                <option value="grid" selected>Grid View</option>
                                <option value="list">List View</option>
                            </select>
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Items per Page</label>
                            <select id="defaultLimitSelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
                                <option value="50">50</option>
                                <option value="100" selected>100</option>
                                <option value="200">200</option>
                                <option value="500">500</option>
                            </select>
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Grid Columns (Desktop)</label>
                            <select id="gridColumnsSelect" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm">
                                <option value="4">4 Columns</option>
                                <option value="6">6 Columns</option>
                                <option value="8" selected>8 Columns</option>
                                <option value="10">10 Columns</option>
                            </select>
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Show Media Info</label>
                            <input type="checkbox" id="showImageInfoToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                    </div>
                    
                    <!-- Video Settings -->
                    <div class="bg-gray-700 rounded-lg p-4 space-y-3">
                        <h5 class="text-white font-medium mb-3">Video Settings</h5>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Autoplay Videos</label>
                            <input type="checkbox" id="videoAutoplayToggle" class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Mute Videos by Default</label>
                            <input type="checkbox" id="videoMuteToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Loop Videos</label>
                            <input type="checkbox" id="videoLoopToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                    </div>
                </div>

                <!-- Metadata Settings -->
                <div class="space-y-4">
                    <h4 class="text-lg font-medium text-gray-100 flex items-center">
                        <span class="mr-2">📋</span>
                        Metadata Settings
                    </h4>
                    
                    <div class="bg-gray-700 rounded-lg p-4 space-y-3">
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Auto-load Metadata</label>
                            <input type="checkbox" id="autoLoadMetadataToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Cache Metadata</label>
                            <input type="checkbox" id="cacheMetadataToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Show File Paths</label>
                            <input type="checkbox" id="showFilePathsToggle" checked class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                    </div>
                </div>

                <!-- Maintenance -->
                <div class="space-y-4">
                    <h4 class="text-lg font-medium text-gray-100 flex items-center">
                        <span class="mr-2">🔧</span>
                        Maintenance
                    </h4>
                    
                    <div class="bg-gray-700 rounded-lg p-4 space-y-3">
                        <div class="flex items-center justify-between">
                            <div>
                                <h5 class="text-white text-sm font-medium">Clear Cache</h5>
                                <p class="text-xs text-gray-400">Clear cached metadata and thumbnails</p>
                            </div>
                            <button id="clearCacheBtn" class="px-3 py-1 bg-red-600 hover:bg-red-700 text-white text-sm rounded transition-colors">
                                Clear
                            </button>
                        </div>
                        <div class="flex items-center justify-between">
                            <div>
                                <h5 class="text-white text-sm font-medium">Rescan Output Folder</h5>
                                <p class="text-xs text-gray-400">Refresh the image list from disk</p>
                            </div>
                            <button id="rescanFolderBtn" class="px-3 py-1 bg-blue-600 hover:bg-blue-700 text-white text-sm rounded transition-colors">
                                Rescan
                            </button>
                        </div>
                        <div class="flex items-center justify-between">
                            <div>
                                <h5 class="text-white text-sm font-medium">Find Duplicate Images</h5>
                                <p class="text-xs text-gray-400">Scan for duplicate images based on file content</p>
                            </div>
                            <button id="scanDuplicatesBtn" class="px-3 py-1 bg-orange-600 hover:bg-orange-700 text-white text-sm rounded transition-colors">
                                Scan
                            </button>
                        </div>
                    </div>
                </div>

                <!-- Advanced Settings -->
                <div class="space-y-4">
                    <h4 class="text-lg font-medium text-gray-100 flex items-center">
                        <span class="mr-2">🔬</span>
                        Advanced
                    </h4>
                    
                    <div class="bg-gray-700 rounded-lg p-4 space-y-3">
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">Debug Mode</label>
                            <input type="checkbox" id="debugModeToggle" class="w-4 h-4 text-violet-600 bg-gray-600 border-gray-500 rounded focus:ring-violet-500">
                        </div>
                        <div class="flex items-center justify-between">
                            <label class="text-sm text-gray-300">API Timeout (seconds)</label>
                            <input type="number" id="apiTimeoutInput" value="30" min="5" max="300" class="px-3 py-1 bg-gray-600 border border-gray-500 rounded text-gray-100 text-sm w-20">
                        </div>
                    </div>
                </div>

            </div>
            <div class="p-6 border-t border-gray-700 flex justify-end space-x-3">
                <button id="resetSettingsBtn" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
                    Reset to Defaults
                </button>
                <button id="saveSettingsBtn" class="px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white font-medium rounded-lg transition-colors">
                    Save Settings
                </button>
            </div>
        </div>
    </div>

    <!-- Duplicates Modal -->
    <div id="duplicatesModal" class="fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50">
        <div class="bg-gray-800 rounded-xl max-w-4xl w-full max-h-[90vh] overflow-hidden mx-4 border border-gray-700">
            <div class="p-6 border-b border-gray-700">
                <div class="flex items-center justify-between">
                    <h3 class="text-xl font-semibold text-gray-100">🔍 Duplicate Images Found</h3>
                    <button id="closeDuplicatesBtn" class="p-2 rounded-lg hover:bg-gray-700 transition-colors">
                        <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
                        </svg>
                    </button>
                </div>
            </div>
            <div class="p-6 overflow-y-auto max-h-[calc(90vh-200px)] custom-scrollbar">
                <div id="duplicatesScanStatus" class="text-center py-8">
                    <div class="inline-flex items-center px-4 py-2 bg-orange-600 rounded-lg">
                        <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
                            <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
                            <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                        </svg>
                        Scanning for duplicate images...
                    </div>
                </div>
                <div id="duplicatesContent" class="hidden">
                    <div class="mb-6 p-4 bg-red-900/20 border border-red-500/50 rounded-lg">
                        <div class="flex items-start">
                            <svg class="w-5 h-5 text-red-400 mt-0.5 mr-3 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
                                <path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"></path>
                            </svg>
                            <div>
                                <h4 class="text-red-300 font-medium">⚠️ Warning: Removing duplicate images cannot be undone!</h4>
                                <p class="text-red-200 text-sm mt-1">When you remove a duplicate image, the selected file will be permanently deleted from disk. Choose carefully which images to keep.</p>
                            </div>
                        </div>
                    </div>
                    <div id="duplicatesList" class="space-y-4">
                        <!-- Duplicates will be populated here -->
                    </div>
                </div>
            </div>
            <div id="duplicatesFooter" class="hidden p-6 border-t border-gray-700 flex justify-between items-center">
                <div class="text-sm text-gray-400">
                    <span id="duplicatesCount">0</span> duplicate image groups found
                </div>
                <div class="flex space-x-3">
                    <button id="selectAllBtn" class="px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white font-medium rounded-lg transition-colors">
                        Select All to Delete
                    </button>
                    <button id="removeDuplicatesBtn" class="px-4 py-2 bg-red-600 hover:bg-red-700 text-white font-medium rounded-lg transition-colors">
                        Delete Selected
                    </button>
                </div>
            </div>
        </div>
    </div>

    <script>
        class GalleryManager {
            constructor() {
                this.images = [];
                this.currentPage = 1;
                this.limit = 100;
                this.total = 0;
                this.viewMode = 'grid'; // 'grid' or 'list'
                this.viewer = null;
                
                this.initializeEventListeners();
                this.loadImages();
                
                // Check thumbnails at startup if enabled
                this.checkThumbnailsAtStartup();
            }

            initializeEventListeners() {
                document.getElementById('refreshBtn').addEventListener('click', () => this.loadImages());
                document.getElementById('limitSelector').addEventListener('change', (e) => this.changeLimit(parseInt(e.target.value)));
                document.getElementById('gridViewBtn').addEventListener('click', () => this.setViewMode('grid'));
                document.getElementById('listViewBtn').addEventListener('click', () => this.setViewMode('list'));
                document.getElementById('settingsBtn').addEventListener('click', () => this.showSettings());
                document.getElementById('retryBtn').addEventListener('click', () => this.loadImages());
                document.getElementById('prevPageBtn').addEventListener('click', () => this.previousPage());
                document.getElementById('nextPageBtn').addEventListener('click', () => this.nextPage());
                document.getElementById('pageInput').addEventListener('change', (e) => this.goToPage(parseInt(e.target.value)));
                document.getElementById('pageInput').addEventListener('keypress', (e) => {
                    if (e.key === 'Enter') {
                        this.goToPage(parseInt(e.target.value));
                    }
                });
                
                // Settings modal event listeners
                document.getElementById('closeSettingsBtn').addEventListener('click', () => this.hideSettings());
                document.getElementById('saveSettingsBtn').addEventListener('click', () => this.saveSettings());
                document.getElementById('resetSettingsBtn').addEventListener('click', () => this.resetSettings());
                document.getElementById('generateThumbnailsBtn').addEventListener('click', () => this.generateThumbnails());
                document.getElementById('clearCacheBtn').addEventListener('click', () => this.clearCache());
                document.getElementById('rescanFolderBtn').addEventListener('click', () => this.rescanFolder());
                document.getElementById('scanDuplicatesBtn').addEventListener('click', () => this.scanDuplicates());
                
                // Duplicates modal event listeners
                document.getElementById('closeDuplicatesBtn').addEventListener('click', () => this.hideDuplicatesModal());
                document.getElementById('selectAllBtn').addEventListener('click', () => this.selectAllDuplicates());
                document.getElementById('removeDuplicatesBtn').addEventListener('click', () => this.removeDuplicates());
                
                // Close modal on outside click
                document.getElementById('settingsModal').addEventListener('click', (e) => {
                    if (e.target.id === 'settingsModal') {
                        this.hideSettings();
                    }
                });
                
                document.getElementById('duplicatesModal').addEventListener('click', (e) => {
                    if (e.target.id === 'duplicatesModal') {
                        this.hideDuplicatesModal();
                    }
                });
            }

            async loadImages() {
                this.showLoading();
                
                try {
                    const offset = (this.currentPage - 1) * this.limit;
                    const response = await fetch(`/prompt_manager/images/output?limit=${this.limit}&offset=${offset}`);
                    
                    if (!response.ok) {
                        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                    }
                    
                    const data = await response.json();
                    
                    if (data.success) {
                        this.images = data.images;
                        this.total = data.total;
                        this.updateStats();
                        this.renderGallery();
                        this.updatePagination();
                        document.getElementById('loadingStatus').textContent = 'Loaded';
                    } else {
                        throw new Error(data.error || 'Failed to load images');
                    }
                    
                } catch (error) {
                    console.error('Error loading images:', error);
                    this.showError(error.message);
                }
            }

            showLoading() {
                document.getElementById('loadingState').classList.remove('hidden');
                document.getElementById('errorState').classList.add('hidden');
                document.getElementById('galleryGrid').classList.add('hidden');
                document.getElementById('galleryList').classList.add('hidden');
                document.getElementById('paginationControls').classList.add('hidden');
                document.getElementById('loadingStatus').textContent = 'Loading...';
            }

            showError(message) {
                document.getElementById('loadingState').classList.add('hidden');
                document.getElementById('errorState').classList.remove('hidden');
                document.getElementById('galleryGrid').classList.add('hidden');
                document.getElementById('galleryList').classList.add('hidden');
                document.getElementById('paginationControls').classList.add('hidden');
                document.getElementById('errorMessage').textContent = message;
                document.getElementById('loadingStatus').textContent = 'Error';
            }

            updateStats() {
                const start = (this.currentPage - 1) * this.limit + 1;
                const end = Math.min(this.currentPage * this.limit, this.total);
                
                document.getElementById('showingStart').textContent = this.images.length > 0 ? start : 0;
                document.getElementById('showingEnd').textContent = end;
                document.getElementById('totalImages').textContent = this.total;
            }

            setViewMode(mode) {
                this.viewMode = mode;
                
                // Update button states
                const gridBtn = document.getElementById('gridViewBtn');
                const listBtn = document.getElementById('listViewBtn');
                
                if (mode === 'grid') {
                    gridBtn.classList.remove('bg-gray-600');
                    gridBtn.classList.add('bg-violet-600');
                    listBtn.classList.remove('bg-violet-600');
                    listBtn.classList.add('bg-gray-600');
                } else {
                    listBtn.classList.remove('bg-gray-600');
                    listBtn.classList.add('bg-violet-600');
                    gridBtn.classList.remove('bg-violet-600');
                    gridBtn.classList.add('bg-gray-600');
                }
                
                this.renderGallery();
            }

            renderGallery() {
                if (this.images.length === 0) {
                    this.showError('No images found in the output folder');
                    return;
                }

                document.getElementById('loadingState').classList.add('hidden');
                document.getElementById('errorState').classList.add('hidden');

                if (this.viewMode === 'grid') {
                    this.renderGridView();
                } else {
                    this.renderListView();
                }

                // Initialize ViewerJS after rendering
                setTimeout(() => this.initializeViewer(), 100);
                
                // Add simple fallback click handlers
                this.addFallbackClickHandlers();
            }

            renderGridView() {
                const grid = document.getElementById('galleryGrid');
                const list = document.getElementById('galleryList');
                
                list.classList.add('hidden');
                grid.classList.remove('hidden');

                grid.innerHTML = this.images.map((image, index) => {
                    // Use thumbnail for display if available, fallback to original
                    const displayUrl = image.thumbnail_url || image.url;
                    const hasThumb = !!image.thumbnail_url;
                    const isVideo = image.is_video || false;
                    const mediaType = image.media_type || 'image';
                    
                    return `
                    <div class="image-item bg-gray-800 rounded-lg overflow-hidden border border-gray-700 hover:border-gray-600 cursor-pointer group">
                        <div class="aspect-square bg-gray-900 overflow-hidden relative">
                            <img src="${displayUrl}" 
                                 alt="${image.filename}" 
                                 class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
                                 loading="lazy"
                                 data-original="${image.url}"
                                 data-thumbnail="${image.thumbnail_url || ''}"
                                 data-caption="${this.formatImageCaption(image)}"
                                 data-media-type="${mediaType}"
                                 data-is-video="${isVideo}"
                                 onerror="this.style.display='none'; this.nextElementSibling.style.display='flex'">
                            <!-- Fallback for failed thumbnails -->
                            <div class="hidden absolute inset-0 bg-gray-700 text-gray-400 flex items-center justify-center">
                                <div class="text-center">
                                    <div class="text-2xl mb-1">${isVideo ? '🎬' : '🖼️'}</div>
                                    <div class="text-xs">Failed to load</div>
                                </div>
                            </div>
                            <div class="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-20 transition-opacity duration-300 flex items-center justify-center">
                                ${isVideo ? `
                                    <div class="w-12 h-12 bg-black bg-opacity-60 rounded-full flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300">
                                        <svg class="w-6 h-6 text-white ml-1" fill="currentColor" viewBox="0 0 24 24">
                                            <path d="M8 5v14l11-7z"/>
                                        </svg>
                                    </div>
                                ` : `
                                    <svg class="w-8 h-8 text-white opacity-0 group-hover:opacity-100 transition-opacity duration-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
                                    </svg>
                                `}
                            </div>
                            ${hasThumb ? '<div class="absolute top-2 right-2 w-3 h-3 bg-green-500 rounded-full" title="Thumbnail available"></div>' : ''}
                            ${isVideo ? '<div class="absolute top-2 left-2 px-2 py-1 bg-red-600 text-white text-xs rounded" title="Video file">VIDEO</div>' : ''}
                        </div>
                        <div class="p-2">
                            <div class="text-xs text-gray-400 truncate" title="${image.filename}">${image.filename}</div>
                            <div class="text-xs text-gray-500">${this.formatFileSize(image.size)}${hasThumb ? ' • Fast' : ''}${isVideo ? ' • Video' : ''}</div>
                        </div>
                    </div>
                `;
                }).join('');
            }

            renderListView() {
                const grid = document.getElementById('galleryGrid');
                const list = document.getElementById('galleryList');
                
                grid.classList.add('hidden');
                list.classList.remove('hidden');

                list.innerHTML = this.images.map((image, index) => {
                    // Use thumbnail for display if available, fallback to original
                    const displayUrl = image.thumbnail_url || image.url;
                    const hasThumb = !!image.thumbnail_url;
                    const isVideo = image.is_video || false;
                    const mediaType = image.media_type || 'image';
                    
                    return `
                    <div class="image-item bg-gray-800 rounded-lg border border-gray-700 hover:border-gray-600 cursor-pointer group flex items-center p-4">
                        <div class="w-16 h-16 bg-gray-900 rounded-lg overflow-hidden flex-shrink-0 mr-4 relative">
                            <img src="${displayUrl}" 
                                 alt="${image.filename}" 
                                 class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
                                 loading="lazy"
                                 data-original="${image.url}"
                                 data-thumbnail="${image.thumbnail_url || ''}"
                                 data-caption="${this.formatImageCaption(image)}"
                                 data-media-type="${mediaType}"
                                 data-is-video="${isVideo}"
                                 onerror="this.innerHTML='<div class=\\'flex items-center justify-center h-full text-gray-400 text-xs\\'>⚠️</div>'">
                            ${hasThumb ? '<div class="absolute top-1 right-1 w-2 h-2 bg-green-500 rounded-full" title="Thumbnail available"></div>' : ''}
                            ${isVideo ? '<div class="absolute top-1 left-1 w-4 h-3 bg-red-600 text-white text-xs flex items-center justify-center rounded" title="Video">▶</div>' : ''}
                        </div>
                        <div class="flex-1 min-w-0">
                            <div class="text-sm font-medium text-gray-100 truncate">${image.filename}${isVideo ? ' 🎬' : ''}</div>
                            <div class="text-xs text-gray-400">${this.formatFileSize(image.size)} • ${new Date(image.modified_time * 1000).toLocaleDateString()}${hasThumb ? ' • Fast' : ''}${isVideo ? ' • Video' : ''}</div>
                            <div class="text-xs text-gray-500 truncate">${image.relative_path}</div>
                        </div>
                        <div class="flex-shrink-0 ml-4">
                            ${isVideo ? `
                                <svg class="w-5 h-5 text-red-400 group-hover:text-red-300 transition-colors" fill="currentColor" viewBox="0 0 24 24">
                                    <path d="M8 5v14l11-7z"/>
                                </svg>
                            ` : `
                                <svg class="w-5 h-5 text-gray-400 group-hover:text-white transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
                                </svg>
                            `}
                        </div>
                    </div>
                `;
                }).join('');
            }

            initializeViewer() {
                // Destroy existing viewer if it exists
                if (this.viewer) {
                    this.viewer.destroy();
                }

                const container = this.viewMode === 'grid' ? document.getElementById('galleryGrid') : document.getElementById('galleryList');
                
                // Find all images in the container
                const images = container.querySelectorAll('img[data-original]');
                
                if (images.length === 0) {
                    console.warn('No images found for viewer initialization');
                    return;
                }

                this.viewer = new Viewer(container, {
                    inline: false,
                    button: true,
                    navbar: true,
                    title: true,
                    // IMPORTANT: Use original images for viewing, not thumbnails
                    url: 'data-original',  // Tell ViewerJS to use data-original attribute
                    toolbar: {
                        zoomIn: 1,
                        zoomOut: 1,
                        oneToOne: 1,
                        reset: 1,
                        prev: 1,
                        play: {
                            show: 1,
                            size: 'large',
                        },
                        next: 1,
                        rotateLeft: 1,
                        rotateRight: 1,
                        flipHorizontal: 1,
                        flipVertical: 1,
                    },
                    className: 'viewer-dark-theme',
                    title: [1, (image, imageData) => `${imageData.alt} (${this.images.length} images) - Original Image`],
                    viewed: (event) => {
                        console.log('ViewerJS opened original image for metadata');
                        // Add metadata sidebar when ViewerJS opens
                        // Use the original image URL from data-original attribute
                        const originalImg = event.detail.originalImage;
                        setTimeout(() => this.addMetadataSidebar(originalImg), 100);
                    },
                    show: function() {
                        console.log('Viewer shown - displaying original image');
                    },
                    shown: function() {
                        console.log('ViewerJS initialization complete - ready for metadata');
                    },
                    hide: function() {
                        // Remove sidebar when viewer closes
                        const sidebar = document.getElementById('metadata-sidebar');
                        if (sidebar) {
                            sidebar.remove();
                        }
                    }
                });

                // Don't add click handlers here - let ViewerJS handle them
            }

            addMetadataSidebar(originalImage) {
                const viewerContainer = document.querySelector('.viewer-container');
                if (!viewerContainer || document.getElementById('metadata-sidebar')) return;

                const sidebar = document.createElement('div');
                sidebar.id = 'metadata-sidebar';
                sidebar.className = 'metadata-sidebar';
                sidebar.innerHTML = `
                    <!-- Collapse Button -->
                    <div class="metadata-collapse-btn" onclick="this.closest('.metadata-sidebar').classList.toggle('collapsed')">
                        <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
                        </svg>
                    </div>
                    
                    <!-- Header -->
                    <div class="flex items-center justify-between p-4 border-b border-gray-700">
                        <div class="flex items-center gap-2">
                            <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
                            </svg>
                            <h1 class="text-lg font-medium text-white">Generation data</h1>
                        </div>
                        <button class="text-xs text-blue-400 hover:text-blue-300 transition-colors metadata-copy-all">
                            📋 COPY ALL
                        </button>
                    </div>
                    
                    <!-- Scrollable Content -->
                    <div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-6">
                        <!-- Loading state -->
                        <div class="text-center text-gray-500 py-8">
                            <div class="w-8 h-8 border-2 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mx-auto mb-2"></div>
                            <p class="text-sm">Loading metadata...</p>
                        </div>
                    </div>
                `;

                document.body.appendChild(sidebar);

                // Load metadata for the current image (ALWAYS use original for metadata)
                const originalImageUrl = originalImage.dataset.original || originalImage.src;
                console.log('Loading metadata from original image:', originalImageUrl);
                this.loadImageMetadata(originalImageUrl, sidebar.querySelector('#metadata-content'));

                // Listen for ViewerJS view changes to update metadata
                this.setupViewerMetadataUpdates(sidebar);
            }

            setupViewerMetadataUpdates(sidebar) {
                // Listen for ViewerJS navigation events
                const viewerContainer = document.querySelector('.viewer-container');
                if (!viewerContainer) return;

                // Override ViewerJS navigation to update metadata
                const observer = new MutationObserver((mutations) => {
                    mutations.forEach((mutation) => {
                        if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
                            const img = mutation.target;
                            if (img.tagName === 'IMG' && img.closest('.viewer-canvas')) {
                                // Find the corresponding original image
                                const imageUrl = img.src;
                                this.loadImageMetadata(imageUrl, sidebar.querySelector('#metadata-content'));
                            }
                        }
                    });
                });

                const canvas = viewerContainer.querySelector('.viewer-canvas');
                if (canvas) {
                    observer.observe(canvas, {
                        childList: true,
                        subtree: true,
                        attributes: true,
                        attributeFilter: ['src']
                    });
                }
            }

            addFallbackClickHandlers() {
                // Add simple click handlers that work even if ViewerJS fails
                const container = this.viewMode === 'grid' ? document.getElementById('galleryGrid') : document.getElementById('galleryList');
                const imageItems = container.querySelectorAll('.image-item');
                
                imageItems.forEach((item, index) => {
                    item.addEventListener('click', (e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        
                        const img = item.querySelector('img[data-original]');
                        if (img) {
                            // ALWAYS use the original media URL for viewing
                            const originalMediaUrl = img.dataset.original;
                            const caption = img.dataset.caption;
                            const isVideo = img.dataset.isVideo === 'true';
                            const mediaType = img.dataset.mediaType || 'image';
                            
                            console.log('Opening original media for viewing:', originalMediaUrl, 'Type:', mediaType);
                            
                            if (isVideo) {
                                // For videos, use our custom video modal
                                this.showVideoModal(originalMediaUrl, caption, index);
                            } else {
                                // For images, try ViewerJS first, fallback to simple modal
                                if (this.viewer) {
                                    try {
                                        this.viewer.view(index);
                                    } catch (e) {
                                        console.warn('ViewerJS failed, using fallback:', e);
                                        this.showSimpleImageModal(originalMediaUrl, caption);
                                    }
                                } else {
                                    this.showSimpleImageModal(originalMediaUrl, caption);
                                }
                            }
                        }
                    });
                });
            }

            async showSimpleImageModal(imageUrl, caption) {
                // Create a modal that matches the screenshot design
                const modal = document.createElement('div');
                modal.className = 'fixed inset-0 bg-black z-50 flex';
                modal.innerHTML = `
                    <!-- Image area with navigation -->
                    <div class="flex-1 flex items-center justify-center relative">
                        <!-- Navigation arrows -->
                        <button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="prevImageBtn">
                            ‹
                        </button>
                        <button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="nextImageBtn">
                            ›
                        </button>
                        
                        <!-- Close button -->
                        <button class="absolute top-4 right-4 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" onclick="this.parentElement.parentElement.remove()">
                            ×
                        </button>
                        
                        <!-- Main image -->
                        <img src="${imageUrl}" alt="${caption}" class="max-w-full max-h-full object-contain" id="modalImage">
                    </div>
                    
                    <!-- Metadata sidebar (matching the screenshot) -->
                    <div class="metadata-sidebar">
                        <!-- Collapse Button -->
                        <div class="metadata-collapse-btn" onclick="this.closest('.metadata-sidebar').classList.toggle('collapsed')">
                            <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
                            </svg>
                        </div>
                        
                        <!-- Header -->
                        <div class="flex items-center justify-between p-4 border-b border-gray-700">
                            <div class="flex items-center gap-2">
                                <svg class="w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
                                </svg>
                                <h1 class="text-lg font-medium text-white">Generation data</h1>
                            </div>
                            <button class="text-xs text-blue-400 hover:text-blue-300 transition-colors metadata-copy-all">
                                📋 COPY ALL
                            </button>
                        </div>
                        
                        <!-- Scrollable Content -->
                        <div id="metadata-content" class="flex-1 overflow-y-auto p-4 space-y-6">
                            <!-- Loading state -->
                            <div class="text-center text-gray-500 py-8">
                                <div class="w-8 h-8 border-2 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mx-auto mb-2"></div>
                                <p class="text-sm">Loading metadata...</p>
                            </div>
                        </div>
                    </div>
                `;
                
                // Close on click outside image area
                modal.addEventListener('click', (e) => {
                    if (e.target === modal || e.target.id === 'modalImage') {
                        modal.remove();
                    }
                });
                
                // Close on Escape key
                const escapeHandler = (e) => {
                    if (e.key === 'Escape') {
                        modal.remove();
                        document.removeEventListener('keydown', escapeHandler);
                    }
                };
                document.addEventListener('keydown', escapeHandler);
                
                document.body.appendChild(modal);
                
                // Add navigation functionality
                this.setupImageNavigation(modal, imageUrl);
                
                // Load and display metadata
                await this.loadImageMetadata(imageUrl, modal.querySelector('#metadata-content'));
            }

            async showVideoModal(videoUrl, caption, videoIndex) {
                const settings = this.getSettings();
                
                // Create a modal for video viewing
                const modal = document.createElement('div');
                modal.className = 'fixed inset-0 bg-black z-50 flex';
                modal.innerHTML = `
                    <!-- Video area with navigation -->
                    <div class="flex-1 flex items-center justify-center relative">
                        <!-- Navigation arrows -->
                        <button class="absolute left-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="prevVideoBtn">
                            ‹
                        </button>
                        <button class="absolute right-4 top-1/2 transform -translate-y-1/2 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" id="nextVideoBtn">
                            ›
                        </button>
                        
                        <!-- Close button -->
                        <button class="absolute top-4 right-4 w-10 h-10 bg-black bg-opacity-50 hover:bg-opacity-70 rounded-full flex items-center justify-center text-white text-xl z-10" onclick="this.parentElement.parentElement.remove()">
                            ×
                        </button>
                        
                        <!-- Video controls -->
                        <div class="absolute bottom-4 right-4 flex space-x-2 z-10">
                            <button id="videoMuteBtn" class="px-3 py-2 bg-black bg-opacity-50 hover:bg-opacity-70 rounded text-white text-sm">
                                🔊
                            </button>
                            <button id="videoLoopBtn" class="px-3 py-2 bg-black bg-opacity-50 hover:bg-opacity-70 rounded text-white text-sm">
                                🔁
                            </button>
                        </div>
                        
                        <!-- Main video -->
                        <video id="modalVideo" 
                               src="${videoUrl}" 
                               class="max-w-full max-h-full object-contain"
                               controls
                               ${settings.videoAutoplay ? 'autoplay' : ''}
                               ${settings.videoMute ? 'muted' : ''}
                               ${settings.videoLoop ? 'loop' : ''}>
                            Your browser does not support the video tag.
                        </video>
                    </div>
                    
                    <!-- Metadata sidebar (videos have limited metadata) -->
                    <div class="metadata-sidebar">
                        <!-- Collapse Button -->
                        <div class="metadata-collapse-btn" onclick="this.closest('.metadata-sidebar').classList.toggle('collapsed')">
                            <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
                            </svg>
                        </div>
                        
                        <!-- Header -->
                        <div class="flex items-center justify-between p-4 border-b border-gray-700">
                            <div class="flex items-center gap-2">
                                <svg class="w-5 h-5 text-red-400" fill="currentColor" viewBox="0 0 24 24">
                                    <path d="M8 5v14l11-7z"/>
                                </svg>
                                <h1 class="text-lg font-medium text-white">Video Info</h1>
                            </div>
                            <button class="text-xs text-blue-400 hover:text-blue-300 transition-colors" onclick="navigator.clipboard.writeText('${videoUrl}').then(() => this.textContent = '✓ Copied!'); setTimeout(() => this.textContent = '📋 COPY URL', 1000)">
                                📋 COPY URL
                            </button>
                        </div>
                        
                        <!-- Video info content -->
                        <div id="video-info-content" class="flex-1 overflow-y-auto p-4 space-y-6">
                            <!-- Video details will be loaded here -->
                        </div>
                    </div>
                `;
                
                // Close on click outside video area
                modal.addEventListener('click', (e) => {
                    if (e.target === modal) {
                        modal.remove();
                    }
                });
                
                // Close on Escape key
                const escapeHandler = (e) => {
                    if (e.key === 'Escape') {
                        modal.remove();
                        document.removeEventListener('keydown', escapeHandler);
                    }
                };
                document.addEventListener('keydown', escapeHandler);
                
                document.body.appendChild(modal);
                
                // Set up video controls
                this.setupVideoControls(modal, settings);
                
                // Add navigation functionality
                this.setupVideoNavigation(modal, videoUrl, videoIndex);
                
                // Load and display video info
                await this.loadVideoInfo(videoUrl, modal.querySelector('#video-info-content'));
            }

            setupVideoControls(modal, settings) {
                const video = modal.querySelector('#modalVideo');
                const muteBtn = modal.querySelector('#videoMuteBtn');
                const loopBtn = modal.querySelector('#videoLoopBtn');
                
                // Mute/unmute toggle
                muteBtn.addEventListener('click', () => {
                    video.muted = !video.muted;
                    muteBtn.textContent = video.muted ? '🔇' : '🔊';
                    
                    // Save setting
                    const newSettings = this.getSettings();
                    newSettings.videoMute = video.muted;
                    this.saveSettings(newSettings);
                });
                
                // Loop toggle
                loopBtn.addEventListener('click', () => {
                    video.loop = !video.loop;
                    loopBtn.style.opacity = video.loop ? '1' : '0.5';
                    
                    // Save setting
                    const newSettings = this.getSettings();
                    newSettings.videoLoop = video.loop;
                    this.saveSettings(newSettings);
                });
                
                // Set initial states
                muteBtn.textContent = video.muted ? '🔇' : '🔊';
                loopBtn.style.opacity = video.loop ? '1' : '0.5';
            }

            setupVideoNavigation(modal, currentVideoUrl, currentIndex) {
                const prevBtn = modal.querySelector('#prevVideoBtn');
                const nextBtn = modal.querySelector('#nextVideoBtn');
                
                // Get all video items from current gallery
                const container = this.viewMode === 'grid' ? document.getElementById('galleryGrid') : document.getElementById('galleryList');
                const videoItems = Array.from(container.querySelectorAll('.image-item img[data-is-video="true"]'));
                
                if (videoItems.length <= 1) {
                    prevBtn.style.display = 'none';
                    nextBtn.style.display = 'none';
                    return;
                }
                
                const currentVideoIndex = videoItems.findIndex(img => img.dataset.original === currentVideoUrl);
                
                prevBtn.addEventListener('click', () => {
                    const prevIndex = currentVideoIndex > 0 ? currentVideoIndex - 1 : videoItems.length - 1;
                    const prevVideo = videoItems[prevIndex];
                    if (prevVideo) {
                        modal.remove();
                        this.showVideoModal(prevVideo.dataset.original, prevVideo.dataset.caption, prevIndex);
                    }
                });
                
                nextBtn.addEventListener('click', () => {
                    const nextIndex = currentVideoIndex < videoItems.length - 1 ? currentVideoIndex + 1 : 0;
                    const nextVideo = videoItems[nextIndex];
                    if (nextVideo) {
                        modal.remove();
                        this.showVideoModal(nextVideo.dataset.original, nextVideo.dataset.caption, nextIndex);
                    }
                });
                
                // Add keyboard navigation
                const keyHandler = (e) => {
                    if (e.key === 'ArrowLeft') {
                        e.preventDefault();
                        prevBtn.click();
                    } else if (e.key === 'ArrowRight') {
                        e.preventDefault();
                        nextBtn.click();
                    }
                };
                
                document.addEventListener('keydown', keyHandler);
                
                // Clean up listener when modal is removed
                const observer = new MutationObserver((mutations) => {
                    mutations.forEach((mutation) => {
                        if (mutation.type === 'childList') {
                            mutation.removedNodes.forEach((node) => {
                                if (node === modal) {
                                    document.removeEventListener('keydown', keyHandler);
                                    observer.disconnect();
                                }
                            });
                        }
                    });
                });
                
                observer.observe(document.body, { childList: true });
            }

            async loadVideoInfo(videoUrl, container) {
                try {
                    // Extract basic video information
                    const fileName = videoUrl.split('/').pop();
                    const fileExt = fileName.split('.').pop().toUpperCase();
                    
                    container.innerHTML = `
                        <!-- File Info -->
                        <div class="space-y-2">
                            <h2 class="text-sm font-medium text-gray-300">File Info</h2>
                            <div class="text-xs text-gray-400 font-mono bg-gray-800 p-2 rounded break-all cursor-pointer hover:bg-gray-700" 
                                 onclick="navigator.clipboard.writeText('${videoUrl}').then(() => this.textContent = 'Copied!'); setTimeout(() => this.textContent = '${fileName}', 1000)"
                                 title="Click to copy path">
                                ${fileName}
                            </div>
                            <div class="text-xs text-gray-500">
                                Type: ${fileExt} Video
                            </div>
                        </div>

                        <!-- Video Properties -->
                        <div class="space-y-2" id="video-properties">
                            <h2 class="text-sm font-medium text-gray-300">Video Properties</h2>
                            <div class="text-xs text-gray-400">
                                Loading video information...
                            </div>
                        </div>

                        <!-- Video Settings -->
                        <div class="space-y-3">
                            <h2 class="text-sm font-medium text-gray-300">Playback Settings</h2>
                            <div class="space-y-2 text-xs">
                                <div class="flex justify-between items-center">
                                    <span class="text-gray-400">Autoplay:</span>
                                    <span class="text-gray-200">${this.getSettings().videoAutoplay ? 'Enabled' : 'Disabled'}</span>
                                </div>
                                <div class="flex justify-between items-center">
                                    <span class="text-gray-400">Muted:</span>
                                    <span class="text-gray-200">${this.getSettings().videoMute ? 'Yes' : 'No'}</span>
                                </div>
                                <div class="flex justify-between items-center">
                                    <span class="text-gray-400">Loop:</span>
                                    <span class="text-gray-200">${this.getSettings().videoLoop ? 'Enabled' : 'Disabled'}</span>
                                </div>
                            </div>
                        </div>

                        <!-- Note about metadata -->
                        <div class="bg-blue-900/20 border border-blue-600 rounded-lg p-3">
                            <h4 class="text-blue-400 font-medium mb-1 text-sm">ℹ️ Video Note</h4>
                            <p class="text-gray-300 text-xs">
                                Videos don't contain the same metadata as PNG images. ComfyUI workflow data is only embedded in PNG outputs.
                            </p>
                        </div>
                    `;
                    
                    // Try to get video metadata from the video element
                    const video = document.querySelector('#modalVideo');
                    if (video) {
                        video.addEventListener('loadedmetadata', () => {
                            const propertiesDiv = container.querySelector('#video-properties');
                            if (propertiesDiv) {
                                const duration = video.duration;
                                const width = video.videoWidth;
                                const height = video.videoHeight;
                                
                                propertiesDiv.innerHTML = `
                                    <h2 class="text-sm font-medium text-gray-300">Video Properties</h2>
                                    <div class="space-y-1 text-xs">
                                        <div class="flex justify-between">
                                            <span class="text-gray-400">Duration:</span>
                                            <span class="text-gray-200">${this.formatDuration(duration)}</span>
                                        </div>
                                        <div class="flex justify-between">
                                            <span class="text-gray-400">Resolution:</span>
                                            <span class="text-gray-200">${width} × ${height}</span>
                                        </div>
                                        <div class="flex justify-between">
                                            <span class="text-gray-400">Aspect Ratio:</span>
                                            <span class="text-gray-200">${(width/height).toFixed(2)}:1</span>
                                        </div>
                                    </div>
                                `;
                            }
                        });
                    }
                    
                } catch (error) {
                    console.error('Error loading video info:', error);
                    container.innerHTML = `
                        <div class="bg-red-900/20 border border-red-600 rounded-lg p-4">
                            <h4 class="text-red-400 font-medium mb-2">❌ Video Info Error</h4>
                            <p class="text-gray-300 text-sm">${error.message}</p>
                        </div>
                    `;
                }
            }

            formatDuration(seconds) {
                if (isNaN(seconds) || seconds === 0) return '0:00';
                
                const hours = Math.floor(seconds / 3600);
                const minutes = Math.floor((seconds % 3600) / 60);
                const secs = Math.floor(seconds % 60);
                
                if (hours > 0) {
                    return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
                } else {
                    return `${minutes}:${secs.toString().padStart(2, '0')}`;
                }
            }

            async loadImageMetadata(imageUrl, container) {
                try {
                    const metadata = await this.extractImageMetadata(imageUrl);
                    this.displayMetadata(metadata, container);
                } catch (error) {
                    console.error('Error loading metadata:', error);
                    container.innerHTML = `
                        <div class="bg-red-900/20 border border-red-600 rounded-lg p-4">
                            <h4 class="text-red-400 font-medium mb-2">❌ Metadata Error</h4>
                            <p class="text-gray-300 text-sm">${error.message}</p>
                        </div>
                    `;
                }
            }

            displayMetadata(metadata, container) {
                if (!metadata) {
                    container.innerHTML = `
                        <div class="text-center text-gray-500 py-8">
                            <svg class="w-16 h-16 mx-auto mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
                            </svg>
                            <p class="text-sm">No metadata found in this image</p>
                        </div>
                    `;
                    return;
                }

                const html = `
                    <!-- File Path -->
                    <div class="space-y-2">
                        <h2 class="text-sm font-medium text-gray-300">File Path</h2>
                        <div class="text-xs text-gray-400 font-mono bg-gray-800 p-2 rounded break-all cursor-pointer hover:bg-gray-700" data-copy-path title="Click to copy">
                            ${metadata.imagePath || 'Unknown'}
                        </div>
                    </div>

                    <!-- Resources used -->
                    <div class="space-y-3">
                        <h2 class="text-sm font-medium text-gray-300">Resources used</h2>
                        <div class="space-y-2 text-xs">
                            <div class="flex justify-between">
                                <span class="text-gray-400">Model:</span>
                                <span class="text-gray-200">${metadata.checkpoint || 'Unknown'}</span>
                            </div>
                        </div>
                    </div>

                    <!-- Prompt Section -->
                    <div class="space-y-3">
                        <div class="flex items-center justify-between">
                            <h2 class="text-sm font-medium text-gray-300">Prompt</h2>
                            <button class="text-xs px-2 py-1 bg-orange-600 hover:bg-orange-700 text-white rounded transition-colors" data-copy-type="positive">
                                COPY PROMPT
                            </button>
                        </div>
                        <div class="text-xs text-gray-200 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto break-words leading-relaxed">
                            ${this.formatPromptText(metadata.positivePrompt)}
                            <div class="mt-2 pt-2 border-t border-gray-700">
                                <button class="text-blue-400 hover:text-blue-300 underline text-xs" data-show-type="positive">
                                    Show more
                                </button>
                            </div>
                        </div>
                    </div>

                    <!-- Negative Prompt -->
                    <div class="space-y-3">
                        <h2 class="text-sm font-medium text-gray-300">Negative prompt</h2>
                        <div class="text-xs text-gray-200 bg-gray-800 p-3 rounded max-h-32 overflow-y-auto break-words leading-relaxed">
                            ${this.formatPromptText(metadata.negativePrompt)}
                            <div class="mt-2 pt-2 border-t border-gray-700">
                                <button class="text-blue-400 hover:text-blue-300 underline text-xs" data-show-type="negative">
                                    Show more
                                </button>
                            </div>
                        </div>
                    </div>

                    <!-- Other metadata -->
                    <div class="space-y-3">
                        <h2 class="text-sm font-medium text-gray-300">Other metadata</h2>
                        <div class="space-y-2 text-xs">
                            <div class="flex justify-between">
                                <span class="text-gray-400">CFG SCALE:</span>
                                <span class="text-gray-200 px-2 py-1 bg-gray-700 rounded text-xs">${metadata.cfgScale || 'Unknown'}</span>
                            </div>
                            <div class="flex justify-between">
                                <span class="text-gray-400">STEPS:</span>
                                <span class="text-gray-200 px-2 py-1 bg-gray-700 rounded text-xs">${metadata.steps || 'Unknown'}</span>
                            </div>
                            <div class="flex justify-between">
                                <span class="text-gray-400">SAMPLER:</span>
                                <span class="text-gray-200 px-2 py-1 bg-gray-700 rounded text-xs">${metadata.sampler || 'Unknown'}</span>
                            </div>
                            <div class="flex justify-between">
                                <span class="text-gray-400">SEED:</span>
                                <span class="text-gray-200 font-mono px-2 py-1 bg-gray-700 rounded text-xs">${metadata.seed || 'Unknown'}</span>
                            </div>
                        </div>
                    </div>

                    <!-- ComfyUI Workflow -->
                    ${metadata.workflow ? `
                        <div class="space-y-3">
                            <h2 class="text-sm font-medium text-gray-300">ComfyUI Workflow</h2>
                            <div class="space-y-2">
                                <button class="text-xs text-blue-400 hover:text-blue-300 underline flex items-center gap-1" data-action="show-workflow">
                                    <span>View Raw Workflow JSON</span>
                                    <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
                                    </svg>
                                </button>
                                <button class="text-xs text-blue-400 hover:text-blue-300 underline flex items-center gap-1" data-action="download-workflow">
                                    <span>Download Workflow</span>
                                    <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
                                    </svg>
                                </button>
                            </div>
                        </div>
                    ` : ''}
                `;

                container.innerHTML = html;
                
                // Store metadata for actions
                this.currentMetadata = metadata;
                
                // Add click handlers
                this.setupMetadataActions(container);
            }

            setupImageNavigation(modal, currentImageUrl) {
                // Find current image index
                this.currentImageIndex = this.images.findIndex(img => img.url === currentImageUrl);
                
                const prevBtn = modal.querySelector('#prevImageBtn');
                const nextBtn = modal.querySelector('#nextImageBtn');
                const modalImage = modal.querySelector('#modalImage');
                
                const updateImage = async (newIndex) => {
                    if (newIndex >= 0 && newIndex < this.images.length) {
                        this.currentImageIndex = newIndex;
                        const newImage = this.images[newIndex];
                        // ALWAYS use original URL for viewing and metadata
                        modalImage.src = newImage.url;  // This is always the original URL
                        modalImage.alt = this.formatImageCaption(newImage);
                        
                        // Update metadata using original image
                        const metadataContainer = modal.querySelector('#metadata-content');
                        metadataContainer.innerHTML = `
                            <div class="text-center text-gray-500 py-8">
                                <div class="w-8 h-8 border-2 border-blue-500/30 border-t-blue-500 rounded-full animate-spin mx-auto mb-2"></div>
                                <p class="text-sm">Loading metadata from original image...</p>
                            </div>
                        `;
                        // Load metadata from original image (which has embedded workflow data)
                        await this.loadImageMetadata(newImage.url, metadataContainer);
                    }
                    
                    // Update button states
                    prevBtn.style.opacity = newIndex > 0 ? '1' : '0.5';
                    nextBtn.style.opacity = newIndex < this.images.length - 1 ? '1' : '0.5';
                };
                
                prevBtn.addEventListener('click', () => {
                    if (this.currentImageIndex > 0) {
                        updateImage(this.currentImageIndex - 1);
                    }
                });
                
                nextBtn.addEventListener('click', () => {
                    if (this.currentImageIndex < this.images.length - 1) {
                        updateImage(this.currentImageIndex + 1);
                    }
                });
                
                // Keyboard navigation
                const keyHandler = (e) => {
                    if (e.key === 'ArrowLeft' && this.currentImageIndex > 0) {
                        updateImage(this.currentImageIndex - 1);
                    } else if (e.key === 'ArrowRight' && this.currentImageIndex < this.images.length - 1) {
                        updateImage(this.currentImageIndex + 1);
                    }
                };
                document.addEventListener('keydown', keyHandler);
                
                // Clean up on modal close
                modal.addEventListener('remove', () => {
                    document.removeEventListener('keydown', keyHandler);
                });
                
                // Initial button state
                updateImage(this.currentImageIndex);
            }

            setupMetadataActions(container) {
                // Copy prompt buttons
                const copyButtons = container.querySelectorAll('[data-copy-type]');
                copyButtons.forEach(btn => {
                    btn.addEventListener('click', (e) => {
                        const type = btn.getAttribute('data-copy-type');
                        if (this.currentMetadata) {
                            const text = type === 'positive' ? this.currentMetadata.positivePrompt : this.currentMetadata.negativePrompt;
                            this.copyToClipboard(text);
                        }
                    });
                });

                // File path copy
                const pathEl = container.querySelector('[data-copy-path]');
                if (pathEl) {
                    pathEl.addEventListener('click', () => {
                        if (this.currentMetadata) {
                            this.copyToClipboard(this.currentMetadata.imagePath);
                        }
                    });
                }

                // Show more buttons
                const showMoreBtns = container.querySelectorAll('[data-show-type]');
                showMoreBtns.forEach(btn => {
                    btn.addEventListener('click', (e) => {
                        const type = btn.getAttribute('data-show-type');
                        this.showFullPrompt(type);
                    });
                });

                // Workflow action buttons
                const actionBtns = container.querySelectorAll('[data-action]');
                actionBtns.forEach(btn => {
                    btn.addEventListener('click', (e) => {
                        const action = btn.getAttribute('data-action');
                        if (action === 'show-workflow') {
                            this.showWorkflowData();
                        } else if (action === 'download-workflow') {
                            this.downloadWorkflowJSON();
                        }
                    });
                });

                // Copy all button
                const copyAllBtn = container.closest('.metadata-sidebar').querySelector('.metadata-copy-all');
                if (copyAllBtn) {
                    copyAllBtn.addEventListener('click', () => {
                        this.copyAllMetadata();
                    });
                }
            }

            showFullPrompt(type) {
                if (this.currentMetadata) {
                    const prompt = type === 'positive' ? this.currentMetadata.positivePrompt : this.currentMetadata.negativePrompt;
                    const newWindow = window.open('', '_blank');
                    newWindow.document.write(`
                        <html>
                            <head><title>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</title></head>
                            <body style="background: #111; color: #fff; font-family: monospace; padding: 20px;">
                                <h2>${type.charAt(0).toUpperCase() + type.slice(1)} Prompt</h2>
                                <div style="background: #222; padding: 15px; border-radius: 5px; white-space: pre-wrap; line-height: 1.5;">${prompt}</div>
                                <button onclick="navigator.clipboard.writeText(\`${prompt.replace(/`/g, '\\`')}\`).then(() => alert('Copied!'))" style="margin-top: 20px; padding: 10px 20px; background: #444; color: #fff; border: none; border-radius: 5px; cursor: pointer;">Copy to Clipboard</button>
                            </body>
                        </html>
                    `);
                }
            }

            showWorkflowData() {
                if (this.currentMetadata && this.currentMetadata.workflow) {
                    const newWindow = window.open('', '_blank');
                    newWindow.document.write(`
                        <html>
                            <head><title>ComfyUI Workflow Data</title></head>
                            <body style="background: #111; color: #fff; font-family: monospace; padding: 20px;">
                                <h2>ComfyUI Workflow JSON</h2>
                                <pre style="background: #222; padding: 15px; border-radius: 5px; overflow: auto;">${JSON.stringify(this.currentMetadata.workflow, null, 2)}</pre>
                            </body>
                        </html>
                    `);
                }
            }

            async copyToClipboard(text) {
                try {
                    await navigator.clipboard.writeText(text);
                    this.showNotification('Copied to clipboard!', 'success');
                } catch (err) {
                    console.error('Copy failed:', err);
                    this.showNotification('Copy failed', 'error');
                }
            }

            copyAllMetadata() {
                if (!this.currentMetadata) return;
                
                const allData = `Checkpoint: ${this.currentMetadata.checkpoint || 'Unknown'}
Positive Prompt: ${this.currentMetadata.positivePrompt}
Negative Prompt: ${this.currentMetadata.negativePrompt}
Steps: ${this.currentMetadata.steps || 'Unknown'}
CFG Scale: ${this.currentMetadata.cfgScale || 'Unknown'}
Sampler: ${this.currentMetadata.sampler || 'Unknown'}
Seed: ${this.currentMetadata.seed || 'Unknown'}`;
                
                this.copyToClipboard(allData);
            }

            downloadWorkflowJSON() {
                if (this.currentMetadata && this.currentMetadata.workflow) {
                    const dataStr = JSON.stringify(this.currentMetadata.workflow, null, 2);
                    const dataBlob = new Blob([dataStr], {type: 'application/json'});
                    const url = URL.createObjectURL(dataBlob);
                    const link = document.createElement('a');
                    link.href = url;
                    link.download = 'comfyui_workflow.json';
                    document.body.appendChild(link);
                    link.click();
                    document.body.removeChild(link);
                    URL.revokeObjectURL(url);
                    this.showNotification('Workflow downloaded!', 'success');
                }
            }

            formatPromptText(text) {
                if (!text || text === 'No prompt found' || text === 'No negative prompt found') {
                    return `<span class="text-gray-500 italic">${text || 'No prompt found'}</span>`;
                }
                return this.escapeHtml(text);
            }

            escapeHtml(text) {
                const div = document.createElement('div');
                div.textContent = text;
                return div.innerHTML;
            }

            unescapeHtml(text) {
                const div = document.createElement('div');
                div.innerHTML = text;
                return div.textContent;
            }

            showNotification(message, type = 'info') {
                const notification = document.createElement('div');
                notification.className = `fixed top-4 right-4 px-6 py-4 rounded-lg shadow-lg z-[20000] transition-all duration-300 transform translate-x-full`;
                
                const colors = {
                    success: "bg-green-600 text-white",
                    error: "bg-red-600 text-white",
                    warning: "bg-yellow-600 text-white",
                    info: "bg-blue-600 text-white"
                };
                
                notification.className += ` ${colors[type] || colors.info}`;
                notification.textContent = message;
                
                document.body.appendChild(notification);
                
                setTimeout(() => notification.classList.remove("translate-x-full"), 100);
                setTimeout(() => {
                    notification.classList.add("translate-x-full");
                    setTimeout(() => {
                        if (notification.parentNode) {
                            document.body.removeChild(notification);
                        }
                    }, 300);
                }, 3000);
            }

            formatImageCaption(image) {
                const date = new Date(image.modified_time * 1000);
                return `${image.filename} | ${this.formatFileSize(image.size)} | ${date.toLocaleString()}`;
            }

            async extractImageMetadata(imageUrl) {
                try {
                    // Fetch the image as an array buffer
                    const response = await fetch(imageUrl);
                    const arrayBuffer = await response.arrayBuffer();
                    
                    // Parse PNG metadata
                    const metadata = await this.parsePNGMetadata(arrayBuffer);
                    const comfyData = this.extractComfyUIData(metadata);
                    
                    return this.parseWorkflowData(comfyData, imageUrl);
                } catch (error) {
                    console.error('Error extracting metadata:', error);
                    return null;
                }
            }

            async parsePNGMetadata(arrayBuffer) {
                const dataView = new DataView(arrayBuffer);
                let offset = 8; // Skip PNG signature
                const metadata = {};

                while (offset < arrayBuffer.byteLength - 8) {
                    const length = dataView.getUint32(offset);
                    const type = new TextDecoder().decode(arrayBuffer.slice(offset + 4, offset + 8));
                    
                    if (type === 'tEXt' || type === 'iTXt' || type === 'zTXt') {
                        const chunkData = arrayBuffer.slice(offset + 8, offset + 8 + length);
                        let text;
                        
                        if (type === 'tEXt') {
                            text = new TextDecoder().decode(chunkData);
                        } else if (type === 'iTXt') {
                            // iTXt format: keyword\0compression\0language\0translated_keyword\0text
                            const textData = new TextDecoder().decode(chunkData);
                            const parts = textData.split('\0');
                            if (parts.length >= 5) {
                                metadata[parts[0]] = parts[4];
                            }
                            text = textData;
                        } else if (type === 'zTXt') {
                            // zTXt is compressed - basic parsing (might need proper decompression)
                            text = new TextDecoder().decode(chunkData);
                        }
                        
                        // Parse the text chunk for key-value pairs
                        const nullIndex = text.indexOf('\0');
                        if (nullIndex !== -1) {
                            const key = text.substring(0, nullIndex);
                            const value = text.substring(nullIndex + 1);
                            metadata[key] = value;
                        }
                    }
                    
                    offset += 8 + length + 4; // Move to next chunk (8 = length + type, 4 = CRC)
                }

                return metadata;
            }

            extractComfyUIData(metadata) {
                let workflowData = null;
                let promptData = null;

                // Common ComfyUI metadata field names
                const workflowFields = ['workflow', 'Workflow', 'comfy', 'ComfyUI'];
                const promptFields = ['prompt', 'Prompt', 'parameters', 'Parameters'];

                for (const field of workflowFields) {
                    if (metadata[field]) {
                        try {
                            // Clean NaN values from JSON string before parsing
                            let cleanedJson = metadata[field];
                            cleanedJson = cleanedJson.replace(/:\s*NaN\b/g, ': null');
                            cleanedJson = cleanedJson.replace(/\bNaN\b/g, 'null');
                            
                            workflowData = JSON.parse(cleanedJson);
                            break;
                        } catch (e) {
                            console.log('Failed to parse workflow field:', field);
                        }
                    }
                }

                for (const field of promptFields) {
                    if (metadata[field]) {
                        try {
                            // Clean NaN values from JSON string before parsing
                            let cleanedJson = metadata[field];
                            cleanedJson = cleanedJson.replace(/:\s*NaN\b/g, ': null');
                            cleanedJson = cleanedJson.replace(/\bNaN\b/g, 'null');
                            
                            promptData = JSON.parse(cleanedJson);
                            break;
                        } catch (e) {
                            console.log('Failed to parse prompt field:', field);
                        }
                    }
                }

                return { workflow: workflowData, prompt: promptData };
            }

            parseWorkflowData(comfyData, imagePath) {
                // Extract information from ComfyUI workflow
                let checkpoint = 'Unknown';
                let positivePrompt = 'No prompt found';
                let negativePrompt = 'No negative prompt found';
                let steps = 'Unknown';
                let cfgScale = 'Unknown';
                let sampler = 'Unknown';
                let seed = 'Unknown';

                // Parse prompt data first (more reliable for actual generation parameters)
                if (comfyData.prompt) {
                    const promptNodes = comfyData.prompt;
                    
                    for (const nodeId in promptNodes) {
                        const node = promptNodes[nodeId];
                        
                        // Checkpoint
                        if (node.class_type === 'CheckpointLoaderSimple' && node.inputs) {
                            checkpoint = node.inputs.ckpt_name || checkpoint;
                        }
                        
                        // Prompts - need to identify which is positive vs negative
                        if (node.class_type === 'PromptManager' && node.inputs && node.inputs.text) {
                            // PromptManager typically contains the positive prompt
                            positivePrompt = node.inputs.text;
                        }
                        
                        if (node.class_type === 'CLIPTextEncode' && node.inputs && node.inputs.text) {
                            // Check if this looks like a negative prompt
                            const text = node.inputs.text.toLowerCase();
                            if (text.includes('bad anatomy') || text.includes('unfinished') || 
                                text.includes('censored') || text.includes('weird anatomy') ||
                                text.includes('negative') || text.includes('embedding:')) {
                                negativePrompt = node.inputs.text;
                            } else if (positivePrompt === 'No prompt found') {
                                // If we haven't found a positive prompt yet, this might be it
                                positivePrompt = node.inputs.text;
                            }
                        }
                        
                        // Sampling parameters
                        if (node.class_type === 'KSampler' && node.inputs) {
                            steps = node.inputs.steps || steps;
                            cfgScale = node.inputs.cfg || cfgScale;
                            sampler = node.inputs.sampler_name || sampler;
                            seed = node.inputs.seed || seed;
                        }
                        
                        if (node.class_type === 'KSamplerAdvanced' && node.inputs) {
                            steps = node.inputs.steps || steps;
                            cfgScale = node.inputs.cfg || cfgScale;
                            sampler = node.inputs.sampler_name || sampler;
                            seed = node.inputs.noise_seed || seed;
                        }
                    }
                }

                // Also try to parse from workflow data if we didn't find everything in prompt
                if (comfyData.workflow && comfyData.workflow.nodes) {
                    for (const node of comfyData.workflow.nodes) {
                        if (node.type === 'CheckpointLoaderSimple' && node.widgets_values) {
                            checkpoint = node.widgets_values[0] || checkpoint;
                        }
                        
                        if (node.type === 'CLIPTextEncode' && node.widgets_values && node.widgets_values[0]) {
                            const text = node.widgets_values[0];
                            const textLower = text.toLowerCase();
                            
                            if (textLower.includes('bad anatomy') || textLower.includes('unfinished') || 
                                textLower.includes('censored') || textLower.includes('negative')) {
                                negativePrompt = text;
                            } else if (positivePrompt === 'No prompt found') {
                                positivePrompt = text;
                            }
                        }
                        
                        if ((node.type === 'KSampler' || node.type === 'KSamplerAdvanced') && node.widgets_values) {
                            if (node.widgets_values.length >= 4) {
                                seed = node.widgets_values[0] || seed;
                                steps = node.widgets_values[1] || steps;
                                cfgScale = node.widgets_values[2] || cfgScale;
                                sampler = node.widgets_values[3] || sampler;
                            }
                        }
                    }
                }

                return {
                    checkpoint,
                    positivePrompt,
                    negativePrompt,
                    steps,
                    cfgScale,
                    sampler,
                    seed,
                    workflow: comfyData.workflow,
                    imagePath
                };
            }

            // Settings functionality
            showSettings() {
                this.loadSettingsFromStorage();
                document.getElementById('settingsModal').classList.remove('hidden');
                document.getElementById('settingsModal').classList.add('flex');
                document.body.style.overflow = 'hidden';
            }

            hideSettings() {
                document.getElementById('settingsModal').classList.add('hidden');
                document.getElementById('settingsModal').classList.remove('flex');
                document.body.style.overflow = '';
            }

            loadSettingsFromStorage() {
                const settings = this.getSettings();
                
                // Load checkbox settings
                document.getElementById('lazyLoadingToggle').checked = settings.lazyLoading;
                document.getElementById('autoLoadMetadataToggle').checked = settings.autoLoadMetadata;
                document.getElementById('cacheMetadataToggle').checked = settings.cacheMetadata;
                document.getElementById('showFilePathsToggle').checked = settings.showFilePaths;
                document.getElementById('showImageInfoToggle').checked = settings.showImageInfo;
                document.getElementById('debugModeToggle').checked = settings.debugMode;
                document.getElementById('checkThumbnailsAtStartup').checked = settings.checkThumbnailsAtStartup;
                
                // Load video settings
                document.getElementById('videoAutoplayToggle').checked = settings.videoAutoplay;
                document.getElementById('videoMuteToggle').checked = settings.videoMute;
                document.getElementById('videoLoopToggle').checked = settings.videoLoop;
                
                // Load select settings
                document.getElementById('imageQualitySelect').value = settings.imageQuality;
                document.getElementById('defaultViewModeSelect').value = settings.defaultViewMode;
                document.getElementById('defaultLimitSelect').value = settings.defaultLimit;
                document.getElementById('gridColumnsSelect').value = settings.gridColumns;
                
                // Load input settings
                document.getElementById('apiTimeoutInput').value = settings.apiTimeout;
                
                // Update thumbnail status
                document.getElementById('thumbnailStatus').textContent = settings.thumbnailsGenerated ? 'Generated' : 'Not generated';
            }

            getSettings() {
                const defaultSettings = {
                    lazyLoading: true,
                    imageQuality: 'medium',
                    defaultViewMode: 'grid',
                    defaultLimit: 100,
                    gridColumns: 8,
                    showImageInfo: true,
                    autoLoadMetadata: true,
                    cacheMetadata: true,
                    showFilePaths: true,
                    debugMode: false,
                    apiTimeout: 30,
                    thumbnailsGenerated: false,
                    checkThumbnailsAtStartup: true,
                    // Video settings
                    videoAutoplay: false,
                    videoMute: true,
                    videoLoop: true
                };

                try {
                    const stored = localStorage.getItem('gallerySettings');
                    if (stored) {
                        return { ...defaultSettings, ...JSON.parse(stored) };
                    }
                } catch (e) {
                    console.warn('Failed to load settings from localStorage:', e);
                }

                return defaultSettings;
            }

            saveSettings() {
                const settings = {
                    lazyLoading: document.getElementById('lazyLoadingToggle').checked,
                    imageQuality: document.getElementById('imageQualitySelect').value,
                    defaultViewMode: document.getElementById('defaultViewModeSelect').value,
                    defaultLimit: parseInt(document.getElementById('defaultLimitSelect').value),
                    gridColumns: parseInt(document.getElementById('gridColumnsSelect').value),
                    showImageInfo: document.getElementById('showImageInfoToggle').checked,
                    autoLoadMetadata: document.getElementById('autoLoadMetadataToggle').checked,
                    cacheMetadata: document.getElementById('cacheMetadataToggle').checked,
                    showFilePaths: document.getElementById('showFilePathsToggle').checked,
                    debugMode: document.getElementById('debugModeToggle').checked,
                    apiTimeout: parseInt(document.getElementById('apiTimeoutInput').value),
                    thumbnailsGenerated: this.getSettings().thumbnailsGenerated, // Preserve this
                    checkThumbnailsAtStartup: document.getElementById('checkThumbnailsAtStartup').checked,
                    // Video settings
                    videoAutoplay: document.getElementById('videoAutoplayToggle').checked,
                    videoMute: document.getElementById('videoMuteToggle').checked,
                    videoLoop: document.getElementById('videoLoopToggle').checked
                };

                try {
                    localStorage.setItem('gallerySettings', JSON.stringify(settings));
                    this.showNotification('Settings saved successfully!', 'success');
                    this.hideSettings();
                    
                    // Apply some settings immediately
                    this.applySettings(settings);
                } catch (e) {
                    console.error('Failed to save settings:', e);
                    this.showNotification('Failed to save settings', 'error');
                }
            }

            resetSettings() {
                if (confirm('Reset all settings to defaults? This cannot be undone.')) {
                    localStorage.removeItem('gallerySettings');
                    this.loadSettingsFromStorage();
                    this.showNotification('Settings reset to defaults', 'success');
                }
            }

            applySettings(settings) {
                // Apply grid columns
                this.updateGridColumns(settings.gridColumns);
                
                // Apply default view mode if different
                if (this.viewMode !== settings.defaultViewMode) {
                    this.setViewMode(settings.defaultViewMode);
                }
                
                // Apply default limit if different
                if (this.limit !== settings.defaultLimit) {
                    this.limit = settings.defaultLimit;
                    document.getElementById('limitSelector').value = settings.defaultLimit;
                }
            }

            updateGridColumns(columns) {
                const gridContainer = document.getElementById('galleryGrid');
                if (gridContainer) {
                    // Remove existing column classes
                    gridContainer.className = gridContainer.className.replace(/xl:grid-cols-\d+/g, '');
                    // Add new column class
                    gridContainer.classList.add(`xl:grid-cols-${columns}`);
                }
            }

            async generateThumbnails() {
                const btn = document.getElementById('generateThumbnailsBtn');
                const status = document.getElementById('thumbnailStatus');
                const progressContainer = document.getElementById('thumbnailProgress');
                const progressBar = document.getElementById('progressBar');
                const progressText = document.getElementById('progressText');
                const progressPercent = document.getElementById('progressPercent');
                const progressDetails = document.getElementById('progressDetails');
                
                btn.disabled = true;
                btn.textContent = 'Generating...';
                status.textContent = 'Initializing...';
                progressContainer.classList.remove('hidden');
                
                try {
                    // Start thumbnail generation with progress updates
                    const result = await this.generateThumbnailsWithProgress({
                        quality: document.getElementById('imageQualitySelect').value
                    });
                    
                    // Update settings on success
                    const settings = this.getSettings();
                    settings.thumbnailsGenerated = true;
                    localStorage.setItem('gallerySettings', JSON.stringify(settings));
                    
                    status.textContent = 'Generation completed';
                    progressText.textContent = 'Completed!';
                    progressDetails.textContent = `Generated ${result.count} new, skipped ${result.skipped || 0} existing (${result.total_images} total) in ${result.elapsed_time}s`;
                    
                    this.showNotification('Thumbnails generated successfully!', 'success');
                    
                    // Hide progress after a delay
                    setTimeout(() => {
                        progressContainer.classList.add('hidden');
                    }, 3000);
                    
                } catch (error) {
                    console.error('Thumbnail generation error:', error);
                    status.textContent = 'Generation failed';
                    progressText.textContent = 'Failed';
                    progressDetails.textContent = error.message;
                    progressBar.style.width = '0%';
                    progressBar.classList.add('bg-red-600');
                    
                    this.showNotification('Failed to generate thumbnails: ' + error.message, 'error');
                    
                    // Hide progress after a delay
                    setTimeout(() => {
                        progressContainer.classList.add('hidden');
                        progressBar.classList.remove('bg-red-600');
                    }, 5000);
                } finally {
                    btn.disabled = false;
                    btn.textContent = 'Generate';
                }
            }

            async generateThumbnailsWithProgress(options) {
                const progressBar = document.getElementById('progressBar');
                const progressText = document.getElementById('progressText');
                const progressPercent = document.getElementById('progressPercent');
                const progressDetails = document.getElementById('progressDetails');
                const progressETA = document.getElementById('progressETA');
                const cancelBtn = document.getElementById('cancelThumbnailsBtn');
                
                return new Promise((resolve, reject) => {
                    // Show cancel button
                    cancelBtn.classList.remove('hidden');
                    
                    // Set up Server-Sent Events
                    const eventSource = new EventSource(`/prompt_manager/images/generate-thumbnails/progress?quality=${options.quality}`);
                    
                    let cancelled = false;
                    let resultData = null;
                    
                    // Handle cancellation
                    const cancelHandler = () => {
                        cancelled = true;
                        eventSource.close();
                        cancelBtn.classList.add('hidden');
                        reject(new Error('Thumbnail generation cancelled by user'));
                    };
                    cancelBtn.addEventListener('click', cancelHandler);
                    
                    // Handle scanning/status events
                    eventSource.addEventListener('status', (event) => {
                        if (cancelled) return;
                        const data = JSON.parse(event.data);
                        progressText.textContent = data.message || 'Processing...';
                        progressDetails.textContent = data.phase === 'scanning' ? 'Scanning output folder...' : 'Preparing...';
                    });
                    
                    // Handle start event
                    eventSource.addEventListener('start', (event) => {
                        if (cancelled) return;
                        const data = JSON.parse(event.data);
                        progressText.textContent = `Processing ${data.total_images} images...`;
                        progressDetails.textContent = data.message || `Found ${data.total_images} images to process`;
                    });
                    
                    // Handle progress updates
                    eventSource.addEventListener('progress', (event) => {
                        if (cancelled) return;
                        const data = JSON.parse(event.data);
                        
                        // Update progress bar
                        progressBar.style.width = `${data.percentage}%`;
                        progressPercent.textContent = `${data.percentage}%`;
                        
                        // Update text
                        progressText.textContent = `Processing... (${data.processed}/${data.total_images})`;
                        progressDetails.textContent = `Generated: ${data.generated}, Skipped: ${data.skipped} | Rate: ${data.rate} img/s`;
                        
                        // Update ETA
                        if (data.eta > 0 && data.eta < 3600) {
                            const etaText = data.eta > 60 ? `${Math.floor(data.eta / 60)}m ${Math.floor(data.eta % 60)}s` : `${Math.floor(data.eta)}s`;
                            progressETA.textContent = `ETA: ${etaText}`;
                        } else {
                            progressETA.textContent = '';
                        }
                    });
                    
                    // Handle completion
                    eventSource.addEventListener('complete', (event) => {
                        if (cancelled) return;
                        resultData = JSON.parse(event.data);
                        
                        // Update final progress
                        progressBar.style.width = '100%';
                        progressPercent.textContent = '100%';
                        progressText.textContent = 'Completed!';
                        progressDetails.textContent = resultData.message || `Generated ${resultData.count} new thumbnails`;
                        progressETA.textContent = 'Done';
                        
                        // Clean up
                        eventSource.close();
                        cancelBtn.removeEventListener('click', cancelHandler);
                        cancelBtn.classList.add('hidden');
                        
                        resolve(resultData);
                    });
                    
                    // Handle errors
                    eventSource.addEventListener('error', (event) => {
                        if (cancelled) return;
                        const data = JSON.parse(event.data);
                        
                        // Clean up
                        eventSource.close();
                        cancelBtn.removeEventListener('click', cancelHandler);
                        cancelBtn.classList.add('hidden');
                        
                        reject(new Error(data.error || 'Thumbnail generation failed'));
                    });
                    
                    // Handle EventSource errors
                    eventSource.onerror = (error) => {
                        if (cancelled) return;
                        console.error('EventSource error:', error);
                        
                        // Clean up
                        eventSource.close();
                        cancelBtn.removeEventListener('click', cancelHandler);
                        cancelBtn.classList.add('hidden');
                        
                        reject(new Error('Connection to server lost during thumbnail generation'));
                    };
                });
            }


            async clearCache() {
                if (confirm('Clear PromptManager cache and thumbnails? This will NOT affect your original images.')) {
                    try {
                        // Clear only PromptManager-related browser caches (never touch user images)
                        if ('caches' in window) {
                            const cacheNames = await caches.keys();
                            // Only clear caches that are specifically ours
                            const ourCaches = cacheNames.filter(name => 
                                name.includes('prompt-manager') || 
                                name.includes('gallery') ||
                                name.includes('thumbnail')
                            );
                            await Promise.all(ourCaches.map(name => caches.delete(name)));
                        }
                        
                        // Clear only our localStorage entries (never touch user data)
                        Object.keys(localStorage).forEach(key => {
                            if (key.startsWith('gallery_cache_') || 
                                key.startsWith('metadata_cache_') ||
                                key.startsWith('promptManager_') ||
                                key.startsWith('pm_')) {
                                localStorage.removeItem(key);
                            }
                        });
                        
                        // Call backend to clear server-side thumbnails (only our generated thumbnails)
                        try {
                            const response = await fetch('/prompt_manager/images/clear-thumbnails', {
                                method: 'POST',
                                headers: { 'Content-Type': 'application/json' }
                            });
                            
                            if (response.ok) {
                                const data = await response.json();
                                console.log('Server thumbnails cleared:', data);
                            }
                        } catch (e) {
                            console.warn('Could not clear server thumbnails:', e);
                            // Not a critical error - continue with local cleanup
                        }
                        
                        // Update thumbnail status
                        const settings = this.getSettings();
                        settings.thumbnailsGenerated = false;
                        localStorage.setItem('gallerySettings', JSON.stringify(settings));
                        document.getElementById('thumbnailStatus').textContent = 'Not generated';
                        
                        this.showNotification('PromptManager cache cleared successfully! Original images untouched.', 'success');
                    } catch (error) {
                        console.error('Clear cache error:', error);
                        this.showNotification('Failed to clear cache', 'error');
                    }
                }
            }

            async rescanFolder() {
                const btn = document.getElementById('rescanFolderBtn');
                btn.disabled = true;
                btn.textContent = 'Scanning...';
                
                try {
                    // Force reload images
                    await this.loadImages();
                    this.showNotification('Folder rescanned successfully!', 'success');
                } catch (error) {
                    console.error('Rescan error:', error);
                    this.showNotification('Failed to rescan folder', 'error');
                } finally {
                    btn.disabled = false;
                    btn.textContent = 'Rescan';
                }
            }

            async scanDuplicates() {
                this.showDuplicatesModal();
                
                const btn = document.getElementById('scanDuplicatesBtn');
                btn.disabled = true;
                btn.textContent = 'Scanning...';
                
                try {
                    const response = await fetch('/prompt_manager/scan_duplicates');
                    const data = await response.json();
                    
                    if (data.success) {
                        this.displayDuplicates(data.duplicates || []);
                        this.showNotification(`Found ${data.duplicates?.length || 0} duplicate image groups`, 'info');
                    } else {
                        throw new Error(data.error || 'Failed to scan for duplicate images');
                    }
                } catch (error) {
                    console.error('Duplicate scan error:', error);
                    this.showNotification('Failed to scan for duplicate images', 'error');
                    this.hideDuplicatesModal();
                } finally {
                    btn.disabled = false;
                    btn.textContent = 'Scan';
                }
            }

            showDuplicatesModal() {
                document.getElementById('duplicatesModal').classList.remove('hidden');
                document.getElementById('duplicatesModal').classList.add('flex');
                document.body.style.overflow = 'hidden';
                
                // Reset modal state
                document.getElementById('duplicatesScanStatus').classList.remove('hidden');
                document.getElementById('duplicatesContent').classList.add('hidden');
                document.getElementById('duplicatesFooter').classList.add('hidden');
            }

            hideDuplicatesModal() {
                document.getElementById('duplicatesModal').classList.add('hidden');
                document.getElementById('duplicatesModal').classList.remove('flex');
                document.body.style.overflow = '';
            }

            displayDuplicates(duplicates) {
                const scanStatus = document.getElementById('duplicatesScanStatus');
                const content = document.getElementById('duplicatesContent');
                const footer = document.getElementById('duplicatesFooter');
                const list = document.getElementById('duplicatesList');
                const count = document.getElementById('duplicatesCount');
                
                scanStatus.classList.add('hidden');
                
                if (duplicates.length === 0) {
                    list.innerHTML = '<div class="text-center py-8 text-gray-400">No duplicate images found! 🎉</div>';
                    content.classList.remove('hidden');
                    return;
                }
                
                count.textContent = duplicates.length;
                list.innerHTML = '';
                
                duplicates.forEach((group, groupIndex) => {
                    const groupEl = document.createElement('div');
                    groupEl.className = 'bg-gray-700 rounded-lg p-4 border border-gray-600';
                    
                    groupEl.innerHTML = `
                        <div class="mb-3 text-sm font-medium text-gray-300">
                            Duplicate Group ${groupIndex + 1} (${group.images.length} identical ${group.images[0].media_type}s)
                        </div>
                        <div class="mb-3 p-3 bg-gray-800 rounded text-sm text-gray-300">
                            <strong>Content Hash:</strong> ${group.hash.substring(0, 16)}...
                        </div>
                        <div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
                            ${group.images.map((image, imageIndex) => `
                                <div class="flex items-start p-3 bg-gray-800 rounded border border-gray-600 ${imageIndex === 0 ? 'border-green-500' : ''}">
                                    <div class="flex-shrink-0 mr-3">
                                        ${image.media_type === 'video' ? `
                                            <div class="w-20 h-20 bg-gray-600 rounded flex items-center justify-center relative">
                                                ${image.thumbnail_url ? `
                                                    <img src="${image.thumbnail_url}" alt="Video thumbnail" class="w-full h-full object-cover rounded">
                                                ` : `
                                                    <svg class="w-8 h-8 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
                                                        <path d="M2 6a2 2 0 012-2h6l2 2h6a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6zM14.553 7.106A1 1 0 0014 8v4a1 1 0 00.553.894l2 1A1 1 0 0018 13V7a1 1 0 00-1.447-.894l-2 1z"/>
                                                    </svg>
                                                `}
                                                <div class="absolute bottom-0 right-0 bg-black bg-opacity-75 text-white text-xs px-1 rounded">VIDEO</div>
                                            </div>
                                        ` : `
                                            <img src="${image.thumbnail_url || image.url}" alt="${image.filename}" 
                                                 class="w-20 h-20 object-cover rounded cursor-pointer hover:opacity-80" 
                                                 onclick="this.parentElement.parentElement.querySelector('.image-preview').click()">
                                        `}
                                    </div>
                                    <div class="flex-1 min-w-0">
                                        <div class="text-sm text-gray-300 mb-2">
                                            <div class="font-medium truncate" title="${image.filename}">${image.filename}</div>
                                            <div class="text-xs text-gray-400 mt-1">
                                                <span class="font-medium">Size:</span> ${this.formatFileSize(image.size)} • 
                                                <span class="font-medium">Modified:</span> ${new Date(image.modified_time * 1000).toLocaleDateString()}
                                            </div>
                                            <div class="text-xs text-gray-500 mt-1 truncate" title="${image.relative_path}">
                                                ${image.relative_path}
                                            </div>
                                        </div>
                                        <div class="flex items-center justify-between">
                                            <div class="flex items-center space-x-2">
                                                <button class="image-preview text-xs bg-blue-600 hover:bg-blue-700 text-white px-2 py-1 rounded" 
                                                        onclick="window.open('${image.url}', '_blank')">
                                                    View Full
                                                </button>
                                            </div>
                                            <div class="flex items-center">
                                                ${imageIndex === 0 ? `
                                                    <span class="px-3 py-1 bg-green-600 text-white text-xs rounded">KEEP (Oldest)</span>
                                                ` : `
                                                    <label class="flex items-center">
                                                        <input type="checkbox" class="duplicate-checkbox" data-group="${groupIndex}" data-image-path="${image.path}" 
                                                               class="w-4 h-4 text-red-600 bg-gray-600 border-gray-500 rounded focus:ring-red-500">
                                                        <span class="ml-2 text-sm text-red-300">Delete</span>
                                                    </label>
                                                `}
                                            </div>
                                        </div>
                                    </div>
                                </div>
                            `).join('')}
                        </div>
                    `;
                    
                    list.appendChild(groupEl);
                });
                
                content.classList.remove('hidden');
                footer.classList.remove('hidden');
            }

            selectAllDuplicates() {
                const checkboxes = document.querySelectorAll('.duplicate-checkbox');
                const selectBtn = document.getElementById('selectAllBtn');
                
                const allChecked = Array.from(checkboxes).every(cb => cb.checked);
                
                checkboxes.forEach(cb => {
                    cb.checked = !allChecked;
                });
                
                selectBtn.textContent = allChecked ? 'Select All to Delete' : 'Deselect All';
            }

            async removeDuplicates() {
                const checkboxes = document.querySelectorAll('.duplicate-checkbox:checked');
                
                if (checkboxes.length === 0) {
                    this.showNotification('No duplicate images selected for removal', 'warning');
                    return;
                }
                
                const confirmation = confirm(`Are you sure you want to permanently delete ${checkboxes.length} duplicate image files? This action cannot be undone!`);
                if (!confirmation) return;
                
                const btn = document.getElementById('removeDuplicatesBtn');
                btn.disabled = true;
                btn.textContent = 'Deleting...';
                
                try {
                    const imagePaths = Array.from(checkboxes).map(cb => cb.dataset.imagePath);
                    
                    const response = await fetch('/prompt_manager/delete_duplicate_images', {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json'
                        },
                        body: JSON.stringify({ image_paths: imagePaths })
                    });
                    
                    const data = await response.json();
                    
                    if (data.success) {
                        this.showNotification(`Successfully deleted ${imagePaths.length} duplicate image files`, 'success');
                        this.hideDuplicatesModal();
                        this.loadImages(); // Refresh the gallery
                    } else {
                        throw new Error(data.error || 'Failed to delete duplicate images');
                    }
                } catch (error) {
                    console.error('Remove duplicates error:', error);
                    this.showNotification('Failed to delete duplicate images', 'error');
                } finally {
                    btn.disabled = false;
                    btn.textContent = 'Remove Selected';
                }
            }

            formatFileSize(bytes) {
                if (bytes === 0) return '0 Bytes';
                const k = 1024;
                const sizes = ['Bytes', 'KB', 'MB', 'GB'];
                const i = Math.floor(Math.log(bytes) / Math.log(k));
                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
            }

            changeLimit(newLimit) {
                this.limit = newLimit;
                this.currentPage = 1;
                this.loadImages();
            }

            updatePagination() {
                const totalPages = Math.ceil(this.total / this.limit);
                const hasPrev = this.currentPage > 1;
                const hasNext = this.currentPage < totalPages;

                document.getElementById('prevPageBtn').disabled = !hasPrev;
                document.getElementById('nextPageBtn').disabled = !hasNext;
                document.getElementById('pageInfo').textContent = `of ${totalPages}`;
                
                const pageInput = document.getElementById('pageInput');
                pageInput.value = this.currentPage;
                pageInput.max = totalPages;

                if (totalPages > 1) {
                    document.getElementById('paginationControls').classList.remove('hidden');
                } else {
                    document.getElementById('paginationControls').classList.add('hidden');
                }
            }

            previousPage() {
                if (this.currentPage > 1) {
                    this.currentPage--;
                    this.loadImages();
                }
            }

            nextPage() {
                const totalPages = Math.ceil(this.total / this.limit);
                if (this.currentPage < totalPages) {
                    this.currentPage++;
                    this.loadImages();
                }
            }

            goToPage(page) {
                const totalPages = Math.ceil(this.total / this.limit);
                if (isNaN(page) || page < 1 || page > totalPages) {
                    // Reset to current page if invalid input
                    document.getElementById('pageInput').value = this.currentPage;
                    return;
                }
                
                if (page !== this.currentPage) {
                    this.currentPage = page;
                    this.loadImages();
                }
            }

            async checkThumbnailsAtStartup() {
                // Wait a moment to ensure page is fully loaded
                setTimeout(async () => {
                    const settings = this.getSettings();
                    
                    // Only check if the feature is enabled
                    if (!settings.checkThumbnailsAtStartup) {
                        console.debug('Thumbnail startup check disabled in settings');
                        return;
                    }
                    
                    try {
                        // Check a sample of images to see if any are missing thumbnails
                        const response = await fetch('/prompt_manager/images/output?limit=50&offset=0');
                        const data = await response.json();
                        
                        console.debug('Checking images for missing thumbnails:', data);
                        
                        if (data.success && data.images && data.images.length > 0) {
                            // Count images missing thumbnails
                            let missingThumbnails = 0;
                            for (const image of data.images) {
                                if (!image.thumbnail_url) {
                                    missingThumbnails++;
                                }
                            }
                            
                            console.debug(`Found ${missingThumbnails} images without thumbnails in sample of ${data.images.length}`);
                            
                            // If more than 10% of sampled images are missing thumbnails, show prompt
                            const missingPercentage = (missingThumbnails / data.images.length) * 100;
                            if (missingThumbnails > 0 && missingPercentage > 10) {
                                // Estimate total missing based on sample
                                const estimatedMissing = Math.round((missingThumbnails / data.images.length) * data.total);
                                console.debug(`Estimated ${estimatedMissing} total images missing thumbnails (${missingPercentage.toFixed(1)}% of sample)`);
                                this.showThumbnailGenerationPrompt(data.total, estimatedMissing);
                            } else if (missingThumbnails > 0) {
                                console.debug(`Only ${missingPercentage.toFixed(1)}% of images missing thumbnails, not showing prompt`);
                            } else {
                                console.debug('All sampled images have thumbnails');
                            }
                        } else {
                            console.debug('No images found or failed to fetch images');
                        }
                    } catch (error) {
                        console.debug('Could not check for missing thumbnails at startup:', error);
                    }
                }, 1000); // Wait 1 second for page to settle
            }

            showThumbnailGenerationPrompt(imageCount, missingCount = imageCount) {
                const modal = document.createElement('div');
                modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50';
                modal.innerHTML = `
                    <div class="bg-gray-800 rounded-xl max-w-md w-full mx-4 border border-gray-700">
                        <div class="p-6">
                            <h3 class="text-xl font-semibold text-gray-100 mb-4 flex items-center">
                                <span class="mr-2">🚀</span>
                                Generate Thumbnails?
                            </h3>
                            <p class="text-gray-300 mb-4">
                                Found ${missingCount} images without thumbnails (${imageCount} total images). Would you like to generate thumbnails for faster loading?
                            </p>
                            <p class="text-sm text-gray-500 mb-6">
                                💡 Thumbnails speed up gallery loading significantly. You can change this setting in Gallery Settings.
                            </p>
                            <div class="flex space-x-3">
                                <button id="thumbnailPromptNo" class="flex-1 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors">
                                    Not Now
                                </button>
                                <button id="thumbnailPromptYes" class="flex-1 px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white rounded-lg transition-colors">
                                    Generate
                                </button>
                            </div>
                        </div>
                    </div>
                `;
                
                document.body.appendChild(modal);
                
                // Handle button clicks
                document.getElementById('thumbnailPromptNo').addEventListener('click', () => {
                    document.body.removeChild(modal);
                });
                
                document.getElementById('thumbnailPromptYes').addEventListener('click', () => {
                    // Don't remove modal yet - transform it to show progress
                    this.startThumbnailGenerationInModal(modal, imageCount, missingCount);
                });
                
                // Close on outside click
                modal.addEventListener('click', (e) => {
                    if (e.target === modal) {
                        document.body.removeChild(modal);
                    }
                });
            }

            async startThumbnailGenerationInModal(modal, imageCount, missingCount) {
                // Transform the modal to show progress
                const modalContent = modal.querySelector('.bg-gray-800');
                modalContent.innerHTML = `
                    <div class="p-6">
                        <h3 class="text-xl font-semibold text-gray-100 mb-4 flex items-center">
                            <span class="mr-2">🚀</span>
                            Generating Thumbnails
                        </h3>
                        <p class="text-gray-300 mb-4">
                            Processing ${missingCount} images out of ${imageCount} total...
                        </p>
                        
                        <!-- Progress Section -->
                        <div class="mb-6">
                            <div class="flex items-center justify-between text-sm text-gray-400 mb-2">
                                <span id="modalProgressText">Initializing...</span>
                                <span id="modalProgressPercent">0%</span>
                            </div>
                            <div class="w-full bg-gray-600 rounded-full h-3">
                                <div id="modalProgressBar" class="bg-violet-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
                            </div>
                            <div class="flex items-center justify-between text-xs text-gray-500 mt-2">
                                <span id="modalProgressDetails">Starting thumbnail generation...</span>
                                <span id="modalProgressETA" class="text-gray-600"></span>
                            </div>
                        </div>
                        
                        <!-- Status Messages -->
                        <div id="modalStatusMessages" class="text-sm text-gray-400 mb-4 max-h-32 overflow-y-auto custom-scrollbar">
                            <div class="text-violet-400">• Starting thumbnail generation...</div>
                        </div>
                        
                        <!-- Buttons -->
                        <div class="flex space-x-3">
                            <button id="modalCancelBtn" class="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg transition-colors">
                                Cancel
                            </button>
                            <button id="modalCloseBtn" class="flex-1 px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors hidden">
                                Close
                            </button>
                        </div>
                    </div>
                `;
                
                // Setup progress tracking
                let isCancelled = false;
                const progressText = modal.querySelector('#modalProgressText');
                const progressPercent = modal.querySelector('#modalProgressPercent');
                const progressBar = modal.querySelector('#modalProgressBar');
                const progressDetails = modal.querySelector('#modalProgressDetails');
                const progressETA = modal.querySelector('#modalProgressETA');
                const statusMessages = modal.querySelector('#modalStatusMessages');
                const cancelBtn = modal.querySelector('#modalCancelBtn');
                const closeBtn = modal.querySelector('#modalCloseBtn');
                
                // Add status message helper
                const addStatusMessage = (message, type = 'info') => {
                    const colors = {
                        'info': 'text-gray-400',
                        'success': 'text-green-400',
                        'error': 'text-red-400',
                        'warning': 'text-yellow-400'
                    };
                    const msgEl = document.createElement('div');
                    msgEl.className = colors[type] || colors.info;
                    msgEl.textContent = `• ${message}`;
                    statusMessages.appendChild(msgEl);
                    statusMessages.scrollTop = statusMessages.scrollHeight;
                };
                
                // Handle cancel
                cancelBtn.addEventListener('click', () => {
                    isCancelled = true;
                    addStatusMessage('Cancellation requested...', 'warning');
                    cancelBtn.disabled = true;
                    cancelBtn.textContent = 'Cancelling...';
                });
                
                // Handle close (only appears when done)
                closeBtn.addEventListener('click', () => {
                    document.body.removeChild(modal);
                    // Refresh the gallery to show new thumbnails
                    this.loadImages();
                });
                
                // Disable outside click while processing
                modal.removeEventListener('click', modal.clickHandler);
                
                try {
                    addStatusMessage('Connecting to thumbnail generation service...');
                    
                    // Start thumbnail generation with progress monitoring
                    await this.generateThumbnailsWithProgressInModal({
                        progressText,
                        progressPercent,
                        progressBar,
                        progressDetails,
                        progressETA,
                        addStatusMessage,
                        isCancelled: () => isCancelled
                    });
                    
                    // Show completion
                    if (!isCancelled) {
                        progressText.textContent = 'Completed!';
                        progressPercent.textContent = '100%';
                        progressBar.style.width = '100%';
                        addStatusMessage('Thumbnail generation completed successfully!', 'success');
                        
                        // Update settings
                        const settings = this.getSettings();
                        settings.thumbnailsGenerated = true;
                        localStorage.setItem('gallerySettings', JSON.stringify(settings));
                        
                        // Show notification
                        this.showNotification('Thumbnails generated successfully!', 'success');
                    }
                    
                } catch (error) {
                    console.error('Thumbnail generation failed:', error);
                    addStatusMessage(`Generation failed: ${error.message}`, 'error');
                    progressBar.classList.add('bg-red-600');
                    progressBar.classList.remove('bg-violet-600');
                } finally {
                    // Show close button and hide cancel
                    cancelBtn.classList.add('hidden');
                    closeBtn.classList.remove('hidden');
                    
                    // Re-enable outside click to close
                    setTimeout(() => {
                        modal.addEventListener('click', (e) => {
                            if (e.target === modal) {
                                document.body.removeChild(modal);
                                // Refresh the gallery to show new thumbnails
                                this.loadImages();
                            }
                        });
                    }, 100);
                }
            }

            async generateThumbnailsWithProgressInModal(uiElements) {
                const { progressText, progressPercent, progressBar, progressDetails, progressETA, addStatusMessage, isCancelled } = uiElements;
                
                addStatusMessage('Starting thumbnail generation...');
                
                // Get quality setting
                const quality = this.getSettings().imageQuality || 'medium';
                
                try {
                    // Start the progress stream
                    const response = await fetch('/prompt_manager/images/generate-thumbnails/progress', {
                        method: 'GET',
                        headers: {
                            'Accept': 'text/event-stream',
                            'Cache-Control': 'no-cache'
                        }
                    });
                    
                    if (!response.ok) {
                        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                    }
                    
                    const reader = response.body.getReader();
                    const decoder = new TextDecoder();
                    
                    let buffer = '';
                    let startTime = Date.now();
                    
                    while (true) {
                        if (isCancelled()) {
                            addStatusMessage('Generation cancelled by user', 'warning');
                            break;
                        }
                        
                        const { done, value } = await reader.read();
                        
                        if (done) break;
                        
                        buffer += decoder.decode(value, { stream: true });
                        
                        // Process complete lines
                        let lines = buffer.split('\n');
                        buffer = lines.pop(); // Keep incomplete line in buffer
                        
                        for (let line of lines) {
                            line = line.trim();
                            if (line.startsWith('data: ')) {
                                try {
                                    const data = JSON.parse(line.slice(6));
                                    
                                    if (data.type === 'progress') {
                                        // Update progress UI
                                        const progress = Math.round(data.progress || 0);
                                        progressPercent.textContent = `${progress}%`;
                                        progressBar.style.width = `${progress}%`;
                                        
                                        if (data.status) {
                                            progressText.textContent = data.status;
                                        }
                                        
                                        if (data.processed !== undefined && data.found !== undefined) {
                                            progressDetails.textContent = `Processed: ${data.processed}, Generated: ${data.found}`;
                                        }
                                        
                                        // Calculate ETA
                                        if (progress > 5) {
                                            const elapsed = (Date.now() - startTime) / 1000;
                                            const estimated = (elapsed / progress) * 100;
                                            const remaining = Math.max(0, estimated - elapsed);
                                            
                                            if (remaining > 60) {
                                                progressETA.textContent = `~${Math.round(remaining / 60)}m remaining`;
                                            } else {
                                                progressETA.textContent = `~${Math.round(remaining)}s remaining`;
                                            }
                                        }
                                        
                                        // Add status messages for milestones
                                        if (progress === 25) addStatusMessage('25% complete - processing continues...');
                                        if (progress === 50) addStatusMessage('50% complete - halfway there!');
                                        if (progress === 75) addStatusMessage('75% complete - almost done!');
                                        
                                    } else if (data.type === 'complete') {
                                        addStatusMessage(`Completed: ${data.generated} new, ${data.skipped} skipped, ${data.total} total`, 'success');
                                        addStatusMessage(`Processing time: ${data.elapsed_time}s`, 'info');
                                        break;
                                        
                                    } else if (data.type === 'error') {
                                        throw new Error(data.error || 'Unknown error during generation');
                                    }
                                    
                                } catch (parseError) {
                                    console.debug('Parse error for line:', line, parseError);
                                }
                            }
                        }
                    }
                    
                } catch (error) {
                    addStatusMessage(`Error: ${error.message}`, 'error');
                    throw error;
                }
            }
        }

        // Initialize gallery when page loads
        document.addEventListener('DOMContentLoaded', () => {
            new GalleryManager();
        });
    </script>
</body>
</html>